Creating helper tag with UJS - javascript

Firstly, sorry for my English. I'm Brazilian guy that is improving yet.
I want create a helper tag called "collection_cascading_select".
That helper is similar to "collection_select", but he has one more argument called "source".
The "source" is the other collection in the view.
Ever that other option is select in the "source", a JavaScript function needs run to gets his value. Then populate the "collection_cascading_select" collection agreed of that value.
That gets confusing! I'm one week in this problem and my Brazilian brothers aren't help me.
Thanks!
[EDIT]
#Samo
I get it to work, but with some changes.
var success = function(response) {
for (var item in response){
var id = response[item].breed.id <--------------------
var name = response[item].breed.name <-------------------
var option = $(document.createElement('option')).val(id).html(name)
dependentDropDown.append(option)
}
}
I don't understand how FOR IN works.

It sounds like the answer you're looking for is a custom form builder. You could create your form builder and inherit from the Rails form builder, and then set that form builder as the default across your application. Then you could define an element called dependent_dropdown or cascading_selection, etc. This element would probably take the id of the source dropdown. Your helper would output a collection_select but it would also output some JavaScript that would fire an AJAX call when the source dropdown changes.
Of course, you don't have to do it this way. You could just use a collection_select, add some attributes to the source dropdown (i.e. :class => 'source_for_dependent', :dependent => some_id), and then hook up some JavaScript in your application.js that looks for collections with the source_for_dependent class, and when the onchange event fires it grabs the id from the dependent attribute and fires an AJAX call.
Either way, here's an example of your JavaScript (using jQuery)
$('select.source_for_dependent').change(function() {
var id = // get the id of the dependent dropdown, perhaps by $(this).attr('dependent')
var dependentDropDown = $('#' + id);
dependentDropDown.empty();
var success = function(response) {
for (var item in response) {
var option = $(document.createElement('option')).val(item.val).html(item.text);
dependentDropDown.append(option);
}
}
$.get('/some_controller/some_action/' + $(this).val(), success);
}
The success handler gets passed into jQuery's get method. It takes a JSON response as an argument. We loop through the response, and for each item, we create an option, pulling the value and the text off the item, and we append that to the dependent dropdown. Your controller might look something like this:
def some_action
#obj = SomeClass.find(params[:id])
respond_to do |format|
format.js { render :json => #obj }
end
end
Edit
Which controller you target is up to you. Let's say Dropdown A targets resource A, and Dropdown B targets resource B. An object of type A should have a list of objects of type B. If you're going after the show action for object A, then your as_json method for object A needs to include its B associations. This article shows examples of this.
Otherwise, you could target the index action for resource B. Making B a nested resource of A would be an easy way to key off the id of A to get all objects of type B which have a foreign key that points to A.

Related

Rendering more than just model attributes in Parse JS API (backbone.js) view templates

So I'm trying to use a join table to display a list of data in my Parse app. The javascript API is similar enough to backbone.js that I'm assuming anyone who knows that could help me. I can't show my actual source code but I think I simple twitter-like "user follows user" scenario can answer my question. So assume I have a join table called "follows" that simply contains its own objectId, the id of each user in the relationship, and some meta-data about the relationship (needing metadata is why I'm using a join table, instead of Parse.Relation). I want to have a view that finds all of the users the current user follows and renders an instance of another view for each case. From what I have so far, that would looks something like this.
In the intialize of the top level view (let's call it AllFollowsView), I would have something like this.
var currentUser = Parse.User.current();
var followsQuery = new Parse.Query(Follows);
followsQuery.equalTo("userId", currentUser.id);
followsQuery.find({
success: function(followsResult){
for (var i = 0; i < followsResult.length; i++){
var view = new OneFollowView({model:followsResult[i]});
this.$("#followed-list").append(view.render().el);
}//for loop
},
error: function(error){
console.log("error finding plans query");
}
});
OneFollowsView is just a view that renders an showing data about the relationship and listens for changes on that particular relationship (mainly change or delete in my case). I understand that by passing in the corresponding model with
var view = new OneFollowView({model:followsResult[i]});
I can print out attributes of that model in the OneFollowsView template like this
<li>You are following a user with the id of <%= _.escape(followedUserId) %></li>
My problem is that this only gives me access to the information stored in the "follows" object. How would I pass in the corresponding user models (or any other models that I can query for the id of) into the template so I can access them in the html in the same way. I would like to be able to run queries in one of the views and then access those models in the html. I know I can add attributes to the object before declaring a new instance of the lower level class with that object as the model, but that doesn't help me because I don't want to save it with new attributes attached.
EDIT: My render function for the top level function is empty at the moment. It's initilize function contains this line to render the template. I guess this should probably be in the render function and then I would call render from initialize.
this.$el.html(_.template($("#all-follows-template").html()));
Here's the render for the lower (individual li) view
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
this.delegateEvents();
}
From my understanding this just renders the template to el while parsing the model to JSON and then returns to allow chained calls.
The problem here lies in you render method. When you call this.template in your render method. That method, this.template is a template function returned by calling the _.template function. When you call your this.template method, the properties of the object you pass in will be available as instance variables in your template.
In your case you're passing in the JSON of the object. So, the properties of the model become names of variables available in your template. If you want to expose additional variables to the template you have a couple options: 1) Add to the jsonified model's attributes. 2) Send in the model as a top level variable and any additional variables you may want.
// option 1
render: function() {
var templateArgs = _.extend(this.model.toJSON(), { additionalVar: 'new var' });
var content = this.template(templateArgs);
$(this.el).html(content);
this.delegateEvents();
return this;
}
// option 2
render: function() {
var templateArgs = {
followResult: this.model.toJSON(),
additionalVar: 'new var'
};
var content = this.template(templateArgs);
$(this.el).html(content);
return this;
this.delegateEvents();
}
Either option is reasonable. I would probably go with option 2. Which allows you in the template to say something like:
<li> <%= followResult.someProperty %> <%= additionalVar %> </li>
Hope that helps. :)

How do I serialize data from non-form elements and make it accessable from params[:model]

I have a Sinatra app which loads information from an external API and displays it on a page. This is done in Sinatra which gets the information and puts it a temporary model instance (which is NOT saved), so it is easier to access all its propertys in the view.
Now when the user clicks a link I want the model instance to be saved to the database, which I think only can be done via AJAX etc. because the last request already finished and none of the instances is still alive. I thought I needed to extract all the information of the corresponding HTML elements and make an AJAX-Post to another route.
My problem is now, I want to be able to create(and save) the model using #model = Model.create(params[:model]). It would be clear what to do using a form, but that is not an option because all the data is displayed within a table and each table row is one instance of the model.
How do I serialize the data from the table row in which the clicked link is, so I can use it as described above?
UPDATE
I am using MULTIPLE instances of the object class, each in one tablerow!
I am using DataMapper, only the temporary objects are not stored!
I dont want to clutter up my whole setup!
Did you consider ActiveResource? You can use ActiveResource to maintain object state. If your REST API follows convention it would be very easy to map resource.
Regarding second half sending back data to your controller, you could store in hidden variable(s) and on post it should be easy to construct back the object and persist it to database.
Something like
#model
class MyModel < ActiveResource::Base
# set configs here
end
# To fetch record from REST API in controller or whatever
MyModel.find(1)
#in controller on form submit or AJAX
post "/path" do
MyModel.new(params[:myModel])
end
Update
To maintain state of object without using hidden form
in javascript you can have something like
var myModel = #{myModel.to_json}; #Ruby interpolation in HAML it will depend on templating language
on certain action you can update the JSON object
and to post using AJAX
$.post("post/path", myModel);
More Update
In External JS
function my_js_function(obj) {
/* do something useful here like setting up object hash etc
*/
}
In Ruby Template
<script>
var myObj = #{myObj.json}
my_js_function(myObj);
</script>
I found a pretty easy solution. It was nothing more than getting all the required values from the DOM and putting them into an Array!
application.js:
$(".enable").click(function() {
var table_row = $(this).closest("tr");
var model_array = new Array;
var elements_with_information = jRow.find("[name]");
elements_with_information.each(function() {
// Doing some checking on which kind of element
// it actually is and then basically doing:
model_array.push($(this).text());
});
// Constructing nested array to use `params[:model]`
var data = { "model" : {
"property1": model_array[0],
"property2": model_array[1]
}};
// Now doing the AJAX request
$.ajax({
url: "/model/doshit",
type: "POST",
data: data
});
});

data-win-bind issues: converter only runs once and unable to bind id of element

I have the following html that is bound to an object containing id and status. I want to translate status values into a specific color (hence the converter function convertStatus). I can see the converter work on the first binding, but if I change status in the binding list I do not see any UI update nor do I see convertStatus being subsequently called. My other issue is trying to bind the id property of the first span does not seem to work as expected (perhaps it is not possible to set this value via binding...)
HTML:
<span data-win-bind="id: id">person</span>
<span data-win-bind="textContent: status converter.convertStatus"></span>
Javascript (I have tried using to modify the status value):
// persons === WinJS.Binding.List
// updateStatus is a function that is called as a result of status changing in the system
function updateStatus(data) {
persons.forEach(function(value, index, array) {
if(value.id === data.id) {
value.status = data.status;
persons.notifyMutated(index);
}
}, this);
}
I have seen notifyMutated(index) work for values that are not using a converter.
Updating with github project
Public repo for sample (not-working) - this is a really basic app that has a listview with a set of default data and a function that is executed when the item is clicked. The function attempts to randomize one of the bound fields of the item and call notifyMutated(...) on the list to trigger a visual updated. Even with defining the WinJS.Binding.List({ binding: true }); I do not see updates unless I force it via notifyReload(), which produces a reload-flicker on the listview element.
To answer your two questions:
1) Why can't I set id through binding?
This is deliberately prevented. The WinJS binding system uses the ID to track the element that it's binding to (to avoid leaking DOM elements through dangling bindings). As such, it has to be able to control the id for bound templates.
2) Why isn't the converter firing more than once?
The Binding.List will tell the listview about changes in the contents of the list (items added, removed, or moved around) but it's the responsibility of the individual items to notify the listview about changes in their contents.
You need to have a data object that's bindable. There are a couple of options:
Call WinJS.Binding.as on the elements as you add them to the collection
Turn on binding mode on the Binding.List
The latter is probably easier. Basically, when you create your Binding.List, do this:
var list = new WinJS.Binding.List({binding: true});
That way the List will call binding.as on everything in the list, and things should start updating.
I've found that if I doing the following, I will see updates to the UI post-binding:
var list = new WinJS.Binding.List({binding: true});
var item = WinJS.Binding.as({
firstName: "Billy",
lastName: "Bob"
});
list.push(item);
Later in the application, you can change some values like so:
item.firstName = "Bobby";
item.lastName = "Joe";
...and you will see the changes in the UI
Here's a link on MSDN for more information:
MSDN - WinJS.Binding.as
Regarding setting the value of id.
I found that I was able to set the value of the name attribute, for a <button>.
I had been trying to set id, but that wouldn't work.
HTH
optimizeBindingReferences property
Determines whether or not binding should automatically set the ID of an element. This property should be set to true in apps that use Windows Library for JavaScript (WinJS) binding.
WinJS.Binding.optimizeBindingReferences = true;
source: http://msdn.microsoft.com/en-us/library/windows/apps/jj215606.aspx

Fetching a single Backbone model from server

Say I have a route setup:
'photos/:id' : 'showPhoto'
and somebody shares the url: www.mysite.com/photos/12345 with a friend.
When their friend clicks on the shared link, showPhoto gets called back with 12345 passed as the id. I cant figure out how to fetch the model from the server, because even when setting its id property and calling fetch(), backbone thinks that the model isNew and so the ajax request url is just /photos instead of /photos/12345:
showPhoto: (id) ->
photo = new models.Photo _id:id
photo.fetch #does a GET to /photos, I would have expected it to request /photos/12345
success: () ->
render photo view etc...
Photo = Backbone.Model.extend
idAttribute: '_id'
urlRoot: '/photos'
The model Photo is usually part of a collection, but in this scenario someone is visiting the site directly and only expects to see data for one photo, so the collection is not instantiated in this state of the app.
Is the solution to load the entire collection of photos and then use collection.getById(id)? This just seems way too inefficient when I just want to load the properties for one model.
if you don't have the model as part of a collection, you have to tell the model the full url manually. it won't auto-append the id to the urlRoot that you've specified. you can specify a function as the urlRoot to do this:
Photo = Backbone.Model.extend({
urlRoot: function(){
if (this.isNew()){
return "/photos";
} else {
return "/photos/" + this.id;
}
}
});
Backbone uses the id of the model to determine if it's new or not, so once you set that, this code should work correctly. if it doesn't, you could always check for an id in the if-statement instead of checking isNew.
You do not need to tell backbone whether or not to append the id the url. Per the documentation: http://backbonejs.org/#Model-fetch, you may simply set the urlRoot to the equivalent of the url in a collection.
Backbone will automatically append the desired id to the url, provided you use one of the following methods:
model.set("id", 5); //After initialized
model = new Backbone.Model({id: 5}); //New model
If you manually set the id in the attributes hash or directly on the model, backbone won't be aware of it.
model.id = 5; //Don't do this!
there's already a similar question: "How do I fetch a single model in Backbone?"
my answer there should work for you (and it's in coffeescript)
also remember to check Backbone Model#url documentation, it's all explained there
I would bootstrap the collection (by rendering the following to the page) with just one model in it like this:
photos = new PhotoCollection();
photos.reset([ #Html.ToJson(Model) ]);
Note that the server side code that I use is ASP.Net MVC so use something specific to your server side architecture. Also note that the square brackets are important as they take your singular model and wrap it in an array.
Hope that's helpful.

Rails: Using input from dropdown menu without posting

I'm pretty new to Rails and web dev in general. I need to display two dropdown menus, states and schools, such that schools is only displayed after the user has chosen the state, and schools should only display the schools in the chosen state. What I don't know is how I can use the states choice to decide dynamically what schools to display, without the user having to click Submit. I understand that I may need to use JavaScript, but not knowing JS well, I'm not really sure how to do that. Hope I'm making sense. Thanks!
Here is a simple example of dynamically populating a select based on data structures already in your JavaScript. If you need to perform a server request after the user selects a state and return the list of schools, you'll need different code (and helpfully a library like jQuery).
I think you want to do this with AJAX. I'm not going to customize this for Rails 1 but you should be able to follow the idea. Your first dropdown has a list of states, and each state has a list of schools.
// some js file that's loaded from your layout
// When your states dropdown is changed it fires an ajax call
var success = function(response) {
for (var school in response.schools) {
$('#schools_dropdown').html('');
var option = $(document.createElement('option')).html(school.name).val(school.id);
option.appendTo($('#schools_dropdown'));
}
});
$('#states_dropdown').change(function() {
$.get('/state/' + $(this).val() + '/schools', success);
});
# your schools controller
def index
#schools = State.find(params[:id]).schools
respond_to do |format|
format.js { render :json => #schools }
end
end
So maybe you don't have jQuery and maybe rendering json is different in Rails 1... but the idea is the same. You have some javascript attached to your states dropdown so that when it changes, you pull off the id of that state and make an AJAX call to your controller. The last parameter to that AJAX call is a success function that loops through all the schools sent back by the controller, clears the schools dropdown, and adds options into the dropdown one by one.

Categories