I am trying to add an WatchList feature in the existing code discourse ember rails application
I have addded the following code
Discourse.Route.buildRoutes(function() {
var router = this;
this.resource('watchLists', { path: '/watch_lists' }, function() {
this.resource('watchList', {path: ':watch_list_id'});
});
});
In the ember Controller
Discourse.WatchListsController = Discourse.ObjectController.extend({});
In the ember model
Discourse.WatchList = Discourse.Model.extend({});
Discourse.WatchList.reopenClass({
find: function() {
jQuery.getJSON("watch_lists").then(function(json) {
var watch_lists = json.watch_lists.map(function(attrs) {
return Discourse.WatchList.create(attrs);
});
});
In the ember view js
Discourse.WatchListsView = Ember.View.extend({});
In ember route js
Discourse.WatchListsRoute = Discourse.Route.extend({
model: function() {
return Discourse.WatchList.find();
}
});
When i renderring the handlebars template I am getting an WatchListsController object withot the data we have got from the ajax.
Can any body point out where i am doing wrong.
I see two possible problems.
First, you probably want WatchListsController to extend Discourse.ArrayController, not Discourse.ObjectController.
Second your reopen block is not valid JavaScript in the example code that you posted. I count four { but only two }. You probably want something kind of like this:
Discourse.WatchList.reopenClass({
find: function() {
return jQuery.getJSON("watch_lists").then(function(json) {
return json.watch_lists.map(function(attrs) {
return Discourse.WatchList.create(attrs);
}
});
}
});
Related
I am really struggling with waiting on a subscription to load for a specific route before returning the data to the template. I can see on from the publish on the server that a document is found, but on the client there is no document.
If I do a find().count() on the publish, it shows 1 document found, which is correct, but when I do the count on the subscription, it shows 0 documents.
I have tried a number of different methods, like using subscriptions:function() instead of waitOn:function(), but nothing works.
Collections.js lib:
SinglePackage = new Mongo.Collection("SinglePackage");
SinglePackage.allow({
insert: function(){
return true;
},
update: function(){
return true;
},
remove: function(){
return true;
}
});
Publications.js server:
Meteor.publish("SinglePackage", function(pack_id) {
return Packages.find({shortId: pack_id});
});
Iron Router:
Router.route('/package/:id', {
name: 'package.show',
template: 'Package_page',
layoutTemplate: 'Landing_layout',
waitOn: function() {
return Meteor.subscribe('SinglePackage', this.params.id);
},
data: function() {
return SinglePackage.find();
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
Am I doing something very wrong, or is this just a complicated thing to achieve? One would think that waitOn would make the rest of the function wait until the subscription is ready.
Any help would be highly appreciated.
It appears that the data function is running before the subscription is ready. Even if the data function did run after the subscription was ready, it wouldn't be a reactive data source rendering the pub/sub here pointless. Here's a great article on reactive data sources.
Referring to the example from the Iron Router Docs for subscriptions, you would do something like this:
Router.route('/package/:id', {
subscriptions: function() {
// returning a subscription handle or an array of subscription handles
// adds them to the wait list.
return Meteor.subscribe('SinglePackage', this.params.id);
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
Then in your template.js:
Template.Package_page.helpers({
singlePackage() {
// This is now a reactive data source and will automatically update whenever SinglePackage changes in Mongo.
return Package.find().fetch();
}
});
In your template.html you can now use singlePackage:
<template name="Package_page">
{#with singlePackage} <!-- Use #each if you're singlePackage is an array -->
ID: {_id}
{/with}
</template>
I have followed the 3 parts of the tutorial here and everything is working fine, with one exception. When I go to the "Add the book" page and then navigate to "List books" without saving, the list of books is extended by an empty item. I suspect that the reason is the following code, where the new model is created before saving and not removed if not saving the form. Any ideas how to fix this?
Embertest.BooksNewRoute = Ember.Route.extend({
model: function() {
return this.get('store').createRecord('book');
},
actions: {
create: function() {
var newBook = this.get('currentModel');
newBook.save();
this.transitionTo('books');
}
}
});
From http://emberjs.com/api/classes/Ember.Route.html#method_deactivate
Add a deactivate function on your route. This is called before exiting this current route. So you can roll back the record you just created in the model like this:
model: function(params) {
return this.get('store').createRecord('book');
},
deactivate: function() {
this.currentModel.rollback();
},
I solved this by attaching an action to destroy the record to the willTransition event as below. Would anyone comment if this is the right approach?
Embertest.BooksNewRoute = Ember.Route.extend({
model: function() {
return this.get('store').createRecord('book');
},
actions: {
willTransition: function() {
if (this.currentModel.get('isNew')) {
this.get('currentModel').deleteRecord();
};
},
create: function() {
var newBook = this.get('currentModel');
newBook.save();
this.transitionTo('books');
}
}
});
I am trying to set up my Router to use "hashbang" URLs (#!).
I tried this, but obviously it doesn't work:
App.Router.map(function() {
this.route("index", { path: "!/" });
this.route("otherState", { path: "!/otherState" });
});
Is this possible to do in Ember?
Teddy Zeenny's answer is mostly correct, and registerImplementation seems to be a clean way to implement this. I tried to just edit his answer to make it fully answer the question, but my edit got rejected.
Anyway here is the full code to make Ember use hashbang URLs:
(function() {
var get = Ember.get, set = Ember.set;
Ember.Location.registerImplementation('hashbang', Ember.HashLocation.extend({
getURL: function() {
return get(this, 'location').hash.substr(2);
},
setURL: function(path) {
get(this, 'location').hash = "!"+path;
set(this, 'lastSetURL', "!"+path);
},
onUpdateURL: function(callback) {
var self = this;
var guid = Ember.guidFor(this);
Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
Ember.run(function() {
var path = location.hash.substr(2);
if (get(self, 'lastSetURL') === path) { return; }
set(self, 'lastSetURL', null);
callback(location.hash.substr(2));
});
});
},
formatURL: function(url) {
return '#!'+url;
}
}));
})();
Then once you create your app you need to change the router to utilize the "hashbang" location implementation:
App.Router.reopen({
location: 'hashbang'
})
Extending Ember.HashLocation would be the way to go.
For a clean implementation, you can do the following.
Ember.Location.registerImplementation('hashbang', Ember.HashLocation.extend({
// overwrite what you need, for example:
formatURL: function(url) {
return '#!' + url;
}
// you'll also need to overwrite setURL, getURL, onUpdateURL...
})
Then instruct your App Router to use your custom implementation for location management:
App.Router.reopen({
location: 'hashbang'
})
In the Backbone.js documentation, in the entry for the Router.routes method, it is stated
When the visitor presses the back button, or enters a URL, and a particular route is matched,
the name of the action will be fired as an event, so that other objects can listen to the router,
and be notified.
I have attempted to implement this in this relatively simple example:
The relevant JS:
$(document).ready(function(){
// Thing model
window.Thing = Backbone.Model.extend({
defaults: {
text: 'THIS IS A THING'
}
});
// An individual Thing's View
window.ThingView = Backbone.View.extend({
el: '#thing',
initialize: function() {
this.on('route:showThing', this.anything);
},
anything: function() {
console.log("THIS DOESN'T WORK! WHY?");
},
render: function() {
$(this.el).html(_.template($('#thing-template').html(), {
text: this.model.get('text')
}));
return this;
}
});
// The Router for our App
window.ThingRouter = Backbone.Router.extend({
routes: {
"thing": "showThing"
},
showThing: function() {
console.log('THIS WORKS!');
}
});
// Modified from the code here (from Tim Branyen's boilerplate)
// http://stackoverflow.com/questions/9328513/backbone-js-and-pushstate
window.initializeRouter = function (router, root) {
Backbone.history.start({ pushState: true, root: root });
$(document).on('click', 'a:not([data-bypass])', function (evt) {
var href = $(this).attr('href');
var protocol = this.protocol + '//';
if (href.slice(protocol.length) !== protocol) {
evt.preventDefault();
router.navigate(href, true);
}
});
return router;
}
var myThingView = new ThingView({ model: new Thing() });
myThingView.render();
var myRouter = window.initializeRouter(new ThingRouter(), '/my/path/');
});
The relevant HTML:
<div id="thing"></div>
<!-- Thing Template -->
<script type="text/template" id="thing-template">
<a class='task' href="thing"><%= text %></a>
</script>
However, the router event referenced in the View's initialize function does not seem to get picked up (everything else works--I'm successfully calling the "showThing" method defined in the Router).
I believe I must have some misconception about what the documentation intended by this statement. Therefore, what I'm looking for in a response is: I'd love to have someone revise my code so that it works via a Router event getting picked up by the View, or, clearly explain what the Router documentation I listed above intends us to do, ideally with an alternative code sample (or using mine, modified).
Many thanks in advance for any assistance you can provide!
This is beacuse you are binding a listener to the wrong object. Try this in your View :
window.ThingView = Backbone.View.extend({
initialize: function() {
myRouter.on('route:showThing', this.anything);
},
...
a newbe question:
I've downloaded the backbone boilerplate from https://github.com/david0178418/BackboneJS-AMD-Boilerplate it uses require.js and I wonder about the code navigation during development.
Here is my question:
let's say I have 2 views one extend the other like so:
View 1:
define([
'underscoreLoader',
'backboneLoader',
'text!templates/main.html'
],
function (_, Backbone, MainTemplate) {
"use strict";
return Backbone.View.extend({
template:_.template(MainTemplate),
initialize:function () {
this.render();
},
log:function(msg){
console.log(msg);
},
render:function () {
this.$el.append(this.template({}));
return this;
}
});
}
);
View 2:
define([
'underscoreLoader',
'backboneLoader',
'text!templates/other.html',
'views/main-view'
],
function (_, Backbone, MainTemplate,MainView) {
"use strict";
// how would you navigate to MainView (main-view.js)
return MainView.extend({
template:_.template(MainTemplate),
initialize:function () {
this.render();
},
render:function () {
this.log("my message");
this.$el.append(this.template({}));
return this;
}
});
}
);
Now when I develop (I use IntelliJ) I would like to middle click MainView on the extended view and navigate to the code without having to browse the project tree.
Is that possible using this boilerplate? is there a better approach or a better boilerplate?
I would really like Netbeans's navigator to show me all the methods:
var New_Controller = Backbone.View.extend({
el : null, ...
}
But I can't seem to get it to work. Google came up with something for #lends, but I can't even get Backbone.js to get loaded to the code hint cache.
I ended up installing WebStorm (I saw the IDE in all the egghead.io tutorials) to get the navigator to list all methods and properties.
FYI, Aptana Studio and Zend Studio showed nothing like Netbeans. And Eclipse IDE for JavaScript Web Developers only partially (impractical in real life) works; it flattens the entire hierarchy.
I found this to work fine for me:
the Backbone Objects are wrapped with my custom objects, which allows me to navigate code, extend objects and keep multiple files easily.
Here is how:
Object 1
function ItemModel() {
ItemModel.model = (
Backbone.Model.extend({
initialize:function () {
},
defaults:{
name:"item name"
},
log:function(){
console.log("inherited method");
}
})
);
return new ItemModel.model();
}
Object 2
function ItemModel2() {
ItemModel2.model = (
ItemModel.model.extend({
initialize:function () {
},
defaults:{
name:"item name2"
}
})
);
return new ItemModel2.model();
}
and in my app:
var App = {
item:new ItemModel(),
item2:new ItemModel2()
};