BackboneJS Search Filter System - javascript

I'm using Controller to Fetch URL. I need a way to put Parameter in this POST. These Parameters are selected by users on View & Not Stored yet(I dont know how to store)
Currently I managed to
1) Display & Route The View with search result coming from API
2) Display and refresh the page when someone selects a Filter Option
Problem
1) I got no idea how to record what the users clicked
2) How do i "re-post" so i can get the new set of results
3) I read and say people saying POST Fetch should be done in Model , Conllection is for Store Multiple Models which i don't know in this scenario?
Collections
Jobs.js
define([
'jquery',
'underscore',
'backbone',
'models/filter'
], function($, _, Backbone,JobListFilterModel){
var Jobs = Backbone.Collection.extend({
url: function () {
return 'http://punchgag.com/api/jobs?page='+this.page+''
},
page: 1,
model: JobListFilterModel
});
return Jobs;
});
Collections Filter.JS
define([
'jquery',
'underscore',
'backbone',
'models/filter'
], function($, _, Backbone,JobListFilterModel){
console.log("Loaded");
var Jobs = Backbone.Collection.extend({
url: function () {
return 'http://punchgag.com/api/jobs?page='+this.page+''
},
page: 1,
model: JobListFilterModel
});
// var donuts = new JobListFilterModel;
// console.log(donuts.get("E"));
return Jobs;
});
Models
Filter.js
define([
'underscore',
'backbone'
], function(_, Backbone){
var JobFilterModel = Backbone.Model.extend({
defaults: {
T: '1', //Task / Event-based
PT: '1', //Part-time
C: '1', //Contract
I: '1' //Internship
}
});
// Return the model for the module
return JobFilterModel;
});
Models
Job.js
define([
'underscore',
'backbone'
], function(_, Backbone){
var JobModel = Backbone.Model.extend({
defaults: {
name: "Harry Potter"
}
});
// Return the model for the module
return JobModel;
});
Router.js
define([
'jquery',
'underscore',
'backbone',
'views/jobs/list',
'views/jobs/filter'
], function($, _, Backbone, JobListView, JobListFilterView){
var AppRouter = Backbone.Router.extend({
routes: {
// Define some URL routes
'seeker/jobs': 'showJobs',
'*actions': 'defaultAction'
},
initialize: function(attr)
{
Backbone.history.start({pushState: true, root: "/"})
},
showJobs: function()
{
var view = new JobListView();
view.$el.appendTo('#bbJobList');
view.render();
console.log(view);
var jobListFilterView = new JobListFilterView();
jobListFilterView.render()
},
defaultAction: function(actions)
{
console.info('defaultAction Route');
console.log('No route:', actions);
}
});
var initialize = function(){
console.log('Router Initialized');// <- To e sure yout initialize method is called
var app_router = new AppRouter();
};
return {
initialize: initialize
};
})
;

Capturing what a user has done should be completed within a view. For example:
filterView.js
var View = Backbone.View.extend({
initialize: function() {
_.extend(this, Backbone.Events);
},
render: function() {
// Your normal render routine
return this;
},
events: {
"submit form": "submit"
},
submit: function() {
var query = $("input[type=search]", this.el).val();
this.trigger("submit", query);
}
});
return View;
...whatever implements filterView and jobCollection...
// Implement our collections, views, etc
var jobCollection = new JobCollection();
var jobsView = new JobsView();
var filterView = new FilterView().render();
// Add listeners on our view
filterView.on("submit", updateJobs);
// Implement the functions for our view (this can be done in a number of ways)
function updateJobs(query) {
jobCollection.fetch({
data: {
query: query
}
}).done(function(response) {
// Technically you could just provide "jobsView.render" to the done() method, but I wanted to be a little more verbose here regarding parameters
jobsView.render(response);
});
}
Your view is only aware of itself (input structure, etc). The thing that implements the view and collection is the broker between these two modules.
You specify parameters within the fetch statement by providing an object which implements a data parameter. In this case, we're going to pass the query using the query key. fetch returns a promise, so once that's done, we return that response to the jobsView and pass it to the render function, which should be set up to accept the collection.
updateJobs is unaware of the view implementation, all it knows is it expects a query to be included.
This code is not meant to be directly implemented or copy/pasted, but it's meant to provide an outline for how you might accomplish what you're looking to do.

Related

How to reference collection's data in render() when fetched in initialize() in BackboneJS?

I'm trying to get my head around the correct way to organise a view when it's calling multiple collections (some which only need to be fetched initially and some which are triggered by events).
I have a collection 'Genres_collection()' which fetches a list of genres from a database:
Genres_collection:
define([
'jquery',
'underscore',
'backbone',
'models/genre_model'
],function($,_,Backbone,Genre_model){
var Genres_collection = Backbone.Collection.extend({
model: Genre_model,
url: '/api/genres',
parse: function(response){
return response;
}
})
return Genres_collection;
});
Genres_model:
define([
'jquery',
'underscore',
'backbone'
],function($,_,Backbone){
var Genre_model = Backbone.Model.extend({
defaults: {
'title': 'Untitled Track'
}
})
return Genre_model;
});
I have a view which, amoung other things, I want to fetch the genres on initialize() and then reference the collection's data in render. From my understanding, initialize is triggered only when a view is initialted (thus the name) and render() is called any number of times (ie: when an event is triggered). So, my thought is that render is used to render and 're-render' a view when actions are taken.
Therefore, I have the following view code:
Search_view:
define([
'jquery',
'underscore',
'backbone',
'collections/tracks',
'collections/genres',
'text!templates/search_view_title.html',
'text!templates/search_view.html'
],function($,_,Backbone,Tracks_collection,Genres_collection,Search_view_title_template,Search_view_template){
var Search_view = Backbone.View.extend({
el: $('#app'),
initialize: function(){
this.collection = new Tracks_collection();
this.genre_collection = new Genres_collection();
this.genre_collection.fetch();
},
search: function(searchtype){
switch(searchtype){
case 'genre':
console.log('genre changed');
this.collection.fetch({
data: {limit: 30, type:'genre',genre_id:$('#genre_select').val()}
});
break;
case 'search':
console.log('search changed');
this.collection.fetch({
data: {limit: 30, type:'search',keyword:$('#keyword').val()}
});
break;
}
},
render: function(){
// MAKE 'THIS' ACCESSIBLE
var that = this;
console.log(this.genre_collection);
var template = _.template(Search_view_template);
var template_vars = {genres: this.genre_collection};
console.log(template_vars);
var template_html = template(template_vars);
that.$el.find('#container').html(template_html);
this.collection.bind('add',function(collection){
console.log('collection has been changed');
})
}
});
return Search_view;
});
However, when I try to print the genre_collection in render() to the console or pass it to the template, I don't get the data from the collection which was fetched in initialize().
Is this the correct way to organising this type of call?
Where is it going wrong? How can I get it to pass the data from this.genre_collection in render()?
I am using requirejs and I can confirm that the initialize() and render() call is being called as I have written simpler views which execute the code fine.
In this case, your this = that you should write
console.log(that.genre_collection)
instead of
console.log(this.genre_collection);
Add this to your initialize function
this.listenTo(this.genre_collection , 'change', this.render);
you can change your genre_collection parse nmethod so it will return your data
parse: function(response){
return response.genres
}

Backbone sub views definition - main view vs router

Here is how my Backbone Router looks like
define([
"jquery",
"underscore",
"backbone"
], function ($, _, Backbone) {
return Backbone.Router.extend({
routes: {
"overview": "overview"
},
overview: function () {
require([
"views/overview",
"models/user-collection",
"grid",
"spreadsheet"
], function (OverviewView, TestCollection, GridView, SpreadSheetView) {
// Data
var collection = new TestCollection();
// Main view
var view = new OverviewView({
el: "#page",
collection: collection
});
// Sub view #1
var gridView = new GridView({
el: "#backgridWrapper"
});
// Sub View #2
var spreadsheetView = new SpreadSheetView({
el: "#handsontableWrapper"
});
// Flow
collection.fetch({
success: function () {
view.render();
gridView.render();
spreadsheetView.render();
}
});
});
}
});
});
As you can see there are several views:
Main view
Sub view #1
Sub view #2
I've did a lot of searching on how to organize the views and sub-views in Backbone, however all of them supposed to create a new sub-view instance directly within a view definition, so that router only knows about Main view...
So the question is - is it a good idea to handle sub-views at a router, instead of directly at view constructor?
The router should be just handling routes and initializing stuff.
Things like fetching data should go in the view that uses it - The view displays the data or error messages (in case of a failure), so I think it's wise to let the view fetch the data rather than some router who's only interested in the routes and have no interest in the data.
and I prefer initializing the sub views, inside their parent view, rather than somewhere else. The parent - child relationship itself justifies that, you better keep the children with their parents than a stranger so they will be under better control and you can easily find them later as well :)
Mostly it's a matter of opinion, but the thing is if you don't, all your code will soon get cluttered in the router rather than being well organized.
Below is how I'll structure the same thing.
Note that I'm initializing child views as part of parent views render method. It could be done when the parent view is initialized, but I see no point in doing so unless the parent view successfully fetches data and is proceeding to render itself.
define([
"jquery",
"underscore",
"backbone"
], function($, _, Backbone) {
return Backbone.Router.extend({
routes: {
"overview": "overview"
},
overview: function() {
require(["views/overview"], function(OverviewView) {
// initialize Main view
var view = new OverviewView({
el: "#page"
});
});
}
});
});
define([
"jquery",
"underscore",
"backbone",
"models/user-collection",
"grid",
"spreadsheet"
], function($, _, Backbone, TestCollection, GridView, SpreadSheetView) {
return Backbone.View.extend({
initialize: function(options) {
this.collection = new TestCollection();
this.fetchData();
},
events: {},
render: function() {
// rendering subviews is part of rendering their parent view.
//I prefer to do that here
// Sub view #1
this.gridView = new GridView({
el: "#backgridWrapper"
});
// Sub View #2
this.spreadsheetView = new SpreadSheetView({
el: "#handsontableWrapper"
});
//Below lines can be handled while initializing the respective view
// (In their initialize() method, or after fetching some data etc
// or can be chained with the above initialization if their render() method returns a reference to itself (`return this`)
this.gridView.render();
this.spreadsheetView.render();
},
fetchData: function() {
var view = this;
this.collection.fetch({
success: function() {
view.render();
}
});
}
});
});
side note : I strongly suggest not to put a collection under models folder.

pass parameters to backbone fetch url to deal with a non-standard api

I am trying to manipulate backbone's fetch method to to deal with a bit of a non-standard api. The way the api works is as follows:
api/products/[page]?param1=val&param2=val
ex:
api/products/2?budget=low&categories=all
would be equivalent to getting the second page of results for which the budget is low and all categories are included.
I can pass the parameters after the query string just fine through the format:
self.productsItemsCollection.fetch({ success : onDataHandler, dataType: "json", data: { budget: 'low', categories: 'all' } });
but I'm not sure what to do about the pagination, since it comes before the ? question mark.
Here is how the collection is set up:
define([
'underscore',
'backbone',
'models/products/ProductsItemsModel'
], function(_, Backbone, ProductsItemsModel){
var ProductsItemsCollection = Backbone.Collection.extend({
model: ProductsItemsModel,
initialize : function(models, options) {}, //MH - need to pass filters to this function
url : function() {
return '/api/products/'; //MH - need to pass page number to be appended to this url
},
parse : function(data) {
debugger;
return data.items;
}
});
return ProductsItemsCollection;
});
How do I include the pagination in backbone's fetch command given this api URL structure?
You're on the right track in that Backbone can use the return value of a function as its 'url' value. What I personally would do, is set a page property on the collection (referenced through something like this.page), and include that in the output of the url function.
initialize: function() {
this.page = 1; // Or whatever the default should be
},
url: function() {
return '/api/products/ + this.page;
}
The problem then becomes updating the page property, which can be as simple as 'ProductsItemsCollection.page = 2;'. Personally, I would also add a second fetch method to wrap the page update and fetch into a single method call.
fetch2: function(page, options) {
if (page) {
this.page = page;
}
return this.fetch(options);
}
Just few notes to your code. I think you don't need to define page number into your Collection. According to MVC pattern it's more suitable for Controller. Collection just should get parameter and return some data according to it. Meanwhile Backbone doesn't provide classic MVC Controller, but you can use for this purpose Backbone.View. So structure of your application could looks something like this:
// Collection
define([
'backbone',
'models/products/ProductsItemsModel'
], function(Backbone, ProductsItemsModel){
return Backbone.Collection.extend({
// I don't know what exactly your Model does, but if you don't override Backbone.Model with your own methods you don't really need to define it into your collection.
model: ProductsItemsModel,
initialize : function(models, options) {}, //MH - need to pass filters to this function
url : function(page) {
return '/api/products/' + page;
},
parse : function(data) {
return data.items;
}
});
});
And then in your View you can fetch needed page and render it:
define([
'jquery',
'backbone',
'ProductsItemsCollection'
], function($, Backbone, ProductsItemsCollection){
return Backbone.View.extend({
events: {
// Your logic to get page number from your pagination.
'click .pagination': 'getPageNumber'
}
collection: new ProductsItemsCollection(),
initialize : function() {
this.listenTo(this.collection, 'reset', this.render);
// initial loading collection
this.load(1); // load page #1
},
render: function () {
// your render code
}
// Example function to show how you could get page number.
getPageNumber: function(e) {
var pageNumber = $(e.currentTarget).data('pageNumber');
load(pageNumber);
},
load: function(page) {
url: this.collection.url(page),
data: {
budget: 'low',
categories: 'all'
}
}
});
});
Something like that. So in this View you just make initialization of your Collection and make initial loading. Then all you should make is passing page number to your load function.
I read these answers, i guess they make sense but this is what i went with. just really simple:
app.WorkOrder = Backbone.Collection.extend({
model: app.WorkOrderDetail,
urlRoot: '/m2/api/w/',
getWorkOrder: function(workorder_id, options) {
this.url = this.urlRoot + workorder_id;
return this.fetch(options);
}
});
Then in the view i do this:
app.AppView = Backbone.View.extend({
el: '#workorderapp',
initialize: function () {
app.workOrder.getWorkOrder(workorder_id, {
success:function(data) {
//...do something with data
}
});
},
});

Backbone.js and Require.js - Uncaught TypeError: Object function (){ return parent.apply(this, arguments); } has no method 'on'

I'm putting together a simple todo list app based on some examples to learn about Backbone. I'm running the code on Django for database and using Tastypie to do the API, and am trying to use Backbone (with RequireJS for AMD) but am having trouble with the following error in my console:
Uncaught TypeError: Object function (){ return parent.apply(this, arguments); } has no method 'on'
I think my app is having trouble communicating via the backbone collection,because it seems to get this error at about here in my app.js (main app view):
// Bind relavent events to the todos.
this.listenTo(Todos, "add", this.addOne);
this.listenTo(Todos, "reset", this.addAll);
I've seen some similar issues on here but I'm just breaking into this stuff so an explanation of what's happening would be much appreciated.
collections.js
define([
'underscore',
'backbone',
'app/app',
'app/models/model'
], function(_, Backbone, app, Todo){
// The collection of our todo models.
TodoList = Backbone.Collection.extend({
model: Todo,
// A catcher for the meta object TastyPie will return.
meta: {},
// Set the (relative) url to the API for the item resource.
url: "/api/item",
// Our API will return an object with meta, then objects list.
parse: function(response) {
this.meta = response.meta;
return response.objects;
}
});
var Todos = new TodoList();
return Todos;
});
app.js:
define([
'jquery',
'underscore',
'backbone',
'app/models/model',
'app/collections/collection',
'app/views/view'
], function($, _, Backbone, Todos, Todo, TodoList, TodoView){
// The view for the entire app.
var AppView = Backbone.View.extend({
el: "#todo-app",
// Bind our events.
events: {
"keypress #new-todo": "createOnEnter",
},
initialize: function() {
// TastyPie requires us to use a ?format=json param, so we'll
// set that as a default.
$.ajaxPrefilter(function(options) {
_.extend(options, {format: "json"});
});
// Bind relavent events to the todos.
this.listenTo(Todos, "add", this.addOne);
this.listenTo(Todos, "reset", this.addAll);
// Cache some of our elements.
this.$input = this.$("#new-todo");
// Get our todos from the API!
Todos.fetch();
},
// Crate a new todo when the input has focus and enter key is hit.
createOnEnter: function(event) {
var keyCode = event.keyCode || event.which,
title = this.$input.val().trim();
// If we haven't hit enter, then continue.
if (keyCode != 13 || !title) return;
// Create a new todo.
Todos.create({title: title, complete: false});
// Reset the input.
this.$input.val("");
},
// Add a single todo to the list.
addOne: function(todo) {
var view = new TodoView({model: todo});
$("#todo-list").append(view.render().el);
},
// Clear the list and add each todo one by one.
addAll: function() {
this.$("todo-list").html("");
Todos.each(this.addOne, this);
}
});
return AppView;
});
The order of definitions and parameters in your app.js file is incorrect, you can try like this:
define([
'jquery',
'underscore',
'backbone',
'app/models/model',
'app/collections/collection',
'app/views/view'
], function($, _, Backbone, Todo, Todos, TodoView){

Backbone collection from Json file and cache on localstorage

I am relatively new in the backbone library. I'm trying to build a mobile application based on backbone + requirejs + jquery-mobile. I can fill my collection with existing json local file. (in the future may come from a remote server).
Now I'm trying to get this collection to be called only once and then storing it in localStorage for read. for this I am trying to use this adapter (https://github.com/jeromegn/Backbone.localStorage) but I do not understand how.
Sample code
// models
define([
'underscore',
'backbone'
], function(_, Backbone) {
var AzModel = Backbone.Model.extend({
defaults: {
item: '',
img:"img/gi.jpg"
},
initialize: function(){
}
});
return AzModel;
});
// Collection
define(['jquery', 'underscore', 'backbone', 'models/az'], function($, _, Backbone, AzModel) {
var AzCollection = Backbone.Collection.extend({
localStorage: new Backbone.LocalStorage("AzStore"), // Unique name within your app.
url : "json/azlist.json",
model : AzModel
parse : function(response) {
return response;
}
});
return AzCollection;
});
define(['jquery', 'underscore', 'backbone', 'collections/azlist', 'text!templates/karate/az.html'], function($, _, Backbone, AzList, AzViewTemplate) {
var AzView = Backbone.View.extend({
id:"az",
initialize: function() {
this.collection = new AzList();
var self = this;
this.collection.fetch().done(function() {
//alert("done")
self.render();
});
},
render : function() {
var data = this.collection;
if (data.length == 0) {
// Show's the jQuery Mobile loading icon
$.mobile.loading("show");
} else {
$.mobile.loading("hide");
console.log(data.toJSON());
this.$el.html(_.template(AzViewTemplate, {data:data.toJSON()}));
// create jqueryui
$(document).trigger("create");
}
return this;
}
});
return AzView;
});
Does someone can point me the way.
The Backbone local storage adapter overrides Collection.sync, the function which is used when you fetch the collection, or save models within the collection. If you set the Collection.localStorage property, it redirects the calls to the local storage instead of the server. This means you can have either or -- read and write to local storage or server -- but not both at the same time.
This leaves you two options:
Do the initial fetch, which populates the data from the server, and only then set the localStorage property:
var self = this;
self.collection.fetch().done(function() {
self.collection.localStorage = new Backbone.LocalStorage("AzStore");
self.render();
});
Set the Collection.localStorage property as you do now, and fetch the initial dataset manually using Backbone.ajaxSync, which is the alias given to Backbone.sync by the localstorage adapter:
Backbone.ajaxSync('read', self.collection).done(function() {
self.render();
}
The latter option might be preferrable, because it doesn't prevent you from loading the data from the server later on, if required.
You could quite neatly wrap the functionality as a method on the collection:
var AzCollection = Backbone.Collection.extend({
localStorage: new Backbone.LocalStorage('AzStore'),
refreshFromServer: function() {
return Backbone.ajaxSync('read', this);
}
});
When you want to load data from the server, you can call that method:
collection.refreshFromServer().done(function() { ... });
And when you want to use the local storage, you can use the native fetch:
collection.fetch().done(function() { ... });
Edited to correct mistake in sample code for the benefit of drive-by googlers.

Categories

Resources