I would like to know how to tell backbone to wait until my collection has fetched a model and then render the underscore bit.
In the console returns an error from the underscore template that a field is missing. When I console.log(this.collection.toJSON()) it doesn't show any data. So I assume, that the view is rendered before the data was fetched. How do I tell the view to wait until it is fetched?
/////// View////////
define([
'jquery',
'underscore',
'backbone',
'collections/mitarbeiter',
'text!/aquilamus/templates/mitarbeiter/mitarbeiter.html'
], function($, _, Backbone, MitarbeiterCollection, MitarbeiterTemplate){
var MitarbeiterListView = Backbone.View.extend({
el: $("#container"),
initialize: function(){
this.collection = new MitarbeiterCollection;
this.collection.fetch();
var newtemplate = MitarbeiterTemplate;
this.template = _.template($(newtemplate).html());
},
render: function(){
var renderedContent = this.template(this.collection.toJSON());
$(this.el).html(renderedContent);
return this;
}
});
// Our module now returns our view
return MitarbeiterListView;
});
Using reset like others suggested works, but here is an alternative.
define([
'jquery',
'underscore',
'backbone',
'collections/mitarbeiter',
'text!/aquilamus/templates/mitarbeiter/mitarbeiter.html'
], function($, _, Backbone, MitarbeiterCollection, MitarbeiterTemplate){
var MitarbeiterListView = Backbone.View.extend({
el: $("#container"),
initialize: function(){
this.collection = new MitarbeiterCollection;
var newtemplate = MitarbeiterTemplate;
this.template = _.template($(newtemplate).html());
},
render: function(){
var self = this;
// show some loading message
this.$el.html('Loading');
// fetch, when that is done, replace 'Loading' with content
this.collection.fetch().done(function(){
var renderedContent = self.template(self.collection.toJSON());
self.$el.html(renderedContent);
});
return this;
}
});
// Our module now returns our view
return MitarbeiterListView;
});
Yet another alternative is to do something very similar to this, except use the success callback function which you can put in the fetch options.
This worked the best for me:
var ArtistListView = Backbone.View.extend({
el: '.left',
initialize:function(){
this.render();
},
render: function () {
var source = $('#my-list-template').html();
var template = Handlebars.compile(source);
var collection = new MyCollection;
collection.fetch({async:false});
var html = template(collection.toJSON());
$(this.el).html(html);
});
I do not know any way to say it to wait but I know you can tell backbone to (re)render when your collection is fetched.
initialize: function(){
this.collection = new MitarbeiterCollection;
this.collection.fetch();
var newtemplate = MitarbeiterTemplate;
this.template = _.template($(newtemplate).html());
this.collection.on('reset', this.render, this);
},
The last line listens to your collection's reset event. When it is triggered, it will call the render method.
Collection fetch triggers an event 'reset' when fetch is complete. You can utilise that fetch event
this.collection.on('reset', this.render);
fetch also allow you to pass a success handler function which will be called when fetch succeeds, i.e.:
this.collection.fetch({success: this.render});
Related
I´m having a hard time understanding how to perform this action(as the title says), and maybe someone could help me understand the process, my code is below:
My home-view-model:
var Observable = require("data/observable").Observable;
var ObservableArray = require("data/observable-array").ObservableArray;
var http = require("http");
function createViewModel() {
http.getJSON("http://myJsonfile").then(function (r) {
var arrNoticias = new ObservableArray(r.data);
return arrNoticias;
}, function (e) {
});
}
exports.createViewModel = createViewModel;
I have done a console.log of the arrNoticias before i have putted it inside a callback function and it returns [object object] etc...and then i have done this:
console.log(arrNoticias.getItem(0).titulo);
and it returns the info i need!.
Then in my home.js file i have this:
var observableModule = require("data/observable")
var ObservableArray = require("data/observable-array").ObservableArray;
var arrNoticias = require('./home-view-model.js');
console.log(arrNoticias.getItem(0).titulo);
and the result in the console is:
TypeError: arrNoticias.getItem is not a function. (In 'arrNoticias.getItem(0)', 'arrNoticias.getItem' is undefined)
My question is, how does this action is perform? passing the data from view-model to the .js file?
Thanks for your time
Regards
As that function send a URL request so probably it's an async function, which is on hold while requesting so that's why you get undefined. Normally, you will want your function that sends a URL request to return a promise. Based on that promise, you will the result as expected after the request is done. So:
function createViewModel() {
return new Promise<>((resolve, reject) => {
http.getJSON("http://myJsonfile").then(function (r) {
var arrNoticias = new ObservableArray(r.data);
resolve(arrNoticias);
}, function(e) {
reject(e);
});
}), (e) => {
console.log(e);
})
}
In home.js:
var homeVM= require('./home-view-model.js');
var arrNoticias;
homeVM.createViewModel().then(function(r) {
arrNoticias = r;
});
I have a react component - coursePage.js
function getCourseInitState(){
return {
courses: CourseStore.getAllCourses()//courseStore is required in script
};
}
var Courses = React.createClass({
getInitialState: function(){
return getCourseInitState();
},
render: function () {
return (
<div>
<h1> Course </h1>
<CourseList courses={this.state.courses} />
</div>
);
}
});
Action file -courseAction
var CourseAction = {
CourseList: function(){
var courseList = CourseApi.getAllCourses();
Dispatcher.dispatch({
actionType: ActionTypes.COURSE_INITIALIZE,
courseList: courseList
});
}
Store File - courseStore
var CourseStore = assign({}, EventEmitter.prototype, {
addChangeListener: function(callback){
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback){
this.removeListener(CHANGE_EVENT, callback);
},
emitChange: function(){
this.emit(CHANGE_EVENT);
},
getAllcourses: function(){ //here is the function define
return _courses;
},
getCourseById: function(id){
return _.find(_courses, {id: id});
}
});
Dispatcher.register(function(action){
switch(action.actionType){
case ActionTypes.COURSE_INITIALIZE:
_courses = action.CourseList;
CourseStore.emitChange();
break;
}
});
module.exports = CourseStore;
in console I am getting "Uncaught TypeError: CourseStore.getAllCourses is not a function"
I don't want to call api directly in my coursePage.js so I find this way of initialising the page but it is not working.
(Please note - I am new to this) As per my recent learning Action file must always call API and send the request to State. I can load with help of componentWillMount function. But, I wanted to solve with this.If not wrong, then it is more neat and preferable way of implementing?
You have a typo -> getAllcourses in the Store and in the Component you call getAllCourses
getAllCourses: function(){ //Should be getAllCourses instead of getAllcourses
return _courses;
},
I have a model which represents a list of jobs which are run on the server
I want to poll the server for updates on a timer to show changes to the state of the jobs.
How do I do this?
My Control looks like this
var control = Control({
defaults: {
view: 'app/views/job-index.ejs'
}
}, {
init: function () {
this.element
.empty()
.append(can.view(this.options.view, this.options));
var options = this.options;
window.setInterval(function() {
options.result.refresh();
}, 1000);
},
});
my model, so far looks like this
var model = can.Model({
findOne: 'GET /api/jobs'
}, {
refresh: function() {
// what goes here?
}
});
One observation first:
If you are getting a collection of jobs you should probably want to use findAll instead of findOne:
findAll: 'GET /api/jobs',
findOne: 'GET /api/jobs/{id}'
I understand that result is a single record. So you can do something like:
var Model = can.Model({
findAll: 'GET /api/jobs',
findOne: 'GET /api/jobs/{id}'
}, {
refresh: function () {
var id = this.attr('id');
var self = this;
Model.findOne({id: id}, function (model) {
self.attr(model.attr());
});
}
});
Also, by convention you should name your model class Model not model.
Here is a fiddle http://jsbin.com/xarodoqo/4/edit
I am using the addon-sdk. I have a widget and upon clicking the widget I want to do something with the website (be it modifying the page or reading the deep DOM).
So my thought after reading https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/page-mod#Communicating_With_Content_Scripts would be:
pagemod activated on matched url (in this case ANY)
click on widget which satisfies left-click event. It then emits widget-click.
Pagemod receives widget-click event and fires back an event called from-pagemod.
from-pagemod does something to the webpage.
I see the following output in stdout:
console.log: project: about to emit widget-click
console.log: project: after emitting widget-click
So pagemod didn't receive that event or it was never set up. I am not sure what is missing from this simple test case. Any help is appreciated.
Here is lib/main.js.
var widgets = require('sdk/widget');
var pageMod = require("sdk/page-mod");
var data = require("sdk/self").data;
var tabs = require("sdk/tabs");
exports.main = function() {
var widget = widgets.Widget({
label: "widget label",
id: "widget-id",
contentURL: data.url("off.png"),
contentScriptFile: [data.url("widget.js"), data.url("page.js")]
});
widget.port.on("left-click", function() {
console.log("about to emit widget-click");
widget.port.emit("widget-click", "foo");
console.log("after emitting widget-click");
});
var page = pageMod.PageMod({
include: "*",
contentScriptWhen: "end",
contentScriptFile: data.url("page.js"),
onAttach: function(worker) {
worker.port.on("widget-click", function(msg) {
console.log("on widget-click, ready to emit from-pagemod event");
worker.port.emit("from-pagemod", "foo");
});
}
});
};
Here is page.js
self.port.on("from-pagemod", function(msg) {
console.log("inside from-pagemod listener");
// read DOM or modify the DOM
});
Here is widget.js
this.addEventListener('click', function(event) {
if(event.button == 0 && event.shiftKey == false)
self.port.emit('left-click');
}, true);
Edit 2, Answering the actual question:
If you want to do something to the page on widget click when the content script is already attached, register your widget listener somewhere you have access to the page-worker. You could do this by putting your widget.port.on code inside the pageMod's onAttach(), but then it would only work for the most recently attached page. The best way to make it functional would be to store all workers then check if the current tab has a worker when the widget is clicked, like so:
main.js partial
var workers = [];
widget.port.on("left-click", function() {
console.log("about to emit widget-click");
var worker = getWorker(tabs.activeTab);
if (worker) {
worker.port.emit("widget-click", "foo");
console.log("after emitting widget-click");
}
});
var page = pageMod.PageMod({
include: "*", // TODO: make more specific
contentScriptWhen: "end",
contentScriptFile: data.url("page.js"),
onAttach: function(worker) {
workers.push(worker);
worker.on('detach', function() {
detachWorker(worker);
});
// could be written as
// worker.on('detach', detachWorker.bind(null, worker);
}
});
function detachWorker(worker) {
var index = workers.indexOf(worker);
if(index!==-1) workerArray.splice(index, 1);
}
function getWorker(workers, tab) {
for (var i = workers.length - 1; i >= 0; i--) {
if (workers[i].tab===tab) return worker;
}
}
page.js
self.port.on("widget-click", function(msg) {
console.log("inside widget-click listener");
// read DOM or modify the DOM
});
The reason that your solution wasn't working is that you assumed that events were somehow linked to the file rather than an object.
Old answer: You're making it way too complicated.
Just attach the content script on click (as opposed to adding a content script to every single page that does nothing unless it receives an event)
main.js
var widgets = require('sdk/widget');
var data = require("sdk/self").data;
var tabs = require("sdk/tabs");
exports.main = function() {
var widget = widgets.Widget({
label: "widget label",
id: "widget-id",
contentURL: data.url("off.png"),
contentScriptFile: data.url("widget.js")
});
widget.port.on("left-click", function() {
console.log("about to attach script");
var worker = tabs.activeTab.attach({
contentScriptFile: data.url("page.js");
});
worker.port.on('message from content script', function(someVariable){
//Now I can do something with someVariable in main.js
});
});
};
page.js
// read DOM or modify the DOM
//I'm done, I'll send info back to the main script. This is optional
self.port.emit('message from content script', someVariable);
Edit: Read Modifying the Page Hosted by a Tab for more info. Also, the Tutorials page is a good place to start when you're trying to do something you haven't done before with the SDK. It's a good way to step back and think of alternatives for trying to achieve your goal.
a port is a communication channel between a content script and an add-on component: your Widget may interact with its content via widget.js, and your pagemod with the matched webpage content via page.js. When you do widget.port.emit("widget-click", "foo"); this message can only be listened by widget.js using self.port.on('widget-click') not by the pagemod instance. Since you widget and pagemod are objects that share the main.js scope they can talk each other by just accessing its properties and methods.
I'm working on a site, http://teneo.telegraphbranding.com/, and I am hoping to load the pages via ajax so that the sidebar and its animation remain consistent.
When the 'About' link is clicked I need it to load about2.php via a jquery ajax function. But I'm not having any luck. When I can get the page to load via ajax it kills all the jquery functionality on the page. From what I've read I think I need to call the jquery upon successful completion of the ajax call.
I've tried everything it seems and can't get it work. What I need is about2.php to load when the link is clicked and the jquery to dynamically size the divs, like it does on the homepage.
I tried this, but it won't even load the page:
//Dropdown
$(document).ready(function () {
var dropDown = $('.dropdown');
$('h3.about').on('click', function() {
dropDown.slideDown('fast', function() {
var url = 'about2.php'
$.get(url, function(data) {
//anything in this block runs after the ajax call
var missionWrap = $('#mission-wrap');
var w = $(window);
w.on('load resize',function() {
missionWrap.css({ width:w.width(), height:w.height()});
});
var missionContent = $('#mission-content');
var w = $(window);
w.on('load resize',function() {
missionContent.css({ width:w.width() - 205 });
});
});
});
});
});
And then this loads the page, but kills all the jQuery associated with it:
var dropDown = $('.dropdown');
$('h3.about').on('click', function() {
dropDown.slideDown('fast', function() {
$('#index-wrap').load('/about2.php');
});
});
Thank you very much.
I also tried this and it just broke everything:
$(document).ready(function () {
var dropDown = $('.dropdown');
$('h3.about').on('click', function () {
dropDown.slideDown('fast', function () {
$.ajax({
type: 'GET',
url: 'about2.php',
success: function () {
var missionWrap = $('#mission-wrap');
var w = $(window);
w.on('load resize', function () {
missionWrap.css({
width: w.width(),
height: w.height()
});
});
var missionContent = $('#mission-content');
var w = $(window);
w.on('load resize', function () {
missionContent.css({
width: w.width() - 205
});
});
};
});
});
});
});
This should work.
function resize_me () {
var w = $(window);
var missionWrap = $('#mission-wrap');
var missionContent = $('#mission-content');
missionWrap.css({ width:w.width(), height:w.height()});
missionContent.css({ width:w.width() - 205 });
}
//Dropdown
$(document).ready(function () {
$(window).on('load resize', function () {
resize_me();
});
var dropDown = $('.dropdown');
$('h3.about').on('click', function() {
dropDown.slideDown('fast', function() {
var url = 'about2.php'
$.get(url, function(data) {
resize_me();
});
});
});
}
I believe that the problem was that the load event that you were attaching to the window object was not being triggered by the successful load of the $.get.
This is a rough outline of how to structure your application to handle this correctly.
Assuming your HTML looks something like this:
<html>
...
<div id="index-wrap">
<!-- this is the reloadable part -->
...
</div>
...
</html>
You need to refactor all the jQuery enhancements to the elements inside #index-wrap to be in a function you can call after a reload:
function enhance(root) {
$('#some-button-or-whatever', root).on('click', ...);
}
(I.e. look up all the elements under the root element that was loaded using AJAX.)
You need to call this function when the page is first loaded, as well as after the AJAX call in the completion callback:
$('#index-wrap').load('/foo.php', function() {
enhance(this);
});
You can also potentially get rid of some of this using delegated ("live") events, but it's best to hit the jQuery documentation on how those work.
As for events bound to window or DOM elements which aren't loaded dynamically, you shouldn't need to rebind them at all based on which subpage is loaded, just check which of your elements loaded is in a handler that you set up once:
$(window).on('load resize', function() {
var $missionContent = $('#missionContent');
if ($missionContent.length) {
$missionContent.css(...);
}
});