What's the right way to fill dynamic/static dropdown menus in rails? - ruby-on-rails-3

I've always wondered what do you guys do for filling out dropdown menus in rails, and not have the code splattered in the view. Do you guys make a pivot table? Do you make a class and add methods to return arrays?
I always wonder how other people make them work, for example the other way I need to fill a combo box with all the countries I made a class called DropDownFiller, and added a method called fill_countries that would return an array with all the countries.
What are the best practices regarding this or how do you do it?

The helper options_for_select takes an array of options and builds the select. From the docs:
options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
<option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
options_for_select([ "VISA", "MasterCard" ], "MasterCard")
<option>VISA</option>\n<option selected="selected">MasterCard</option>
options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
<option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
<option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
There are more detailed examples in the documentation.
Depending on how your data is set up, it can be easy to fill a list. For example:
options_for_select Country.select(:name).all.map { |c| c.name }
or for custom values
options_for_select Country.all.map { |c| [c.name, c.code] }
Something else I've seen done a few times is defining a helper method in the model that returns the correct values:
class Country
# awesome country logic goes here!
def self.array_for_select
select(:name).all.map { |c| c.name }
end
end
# a long time ago in a view far far away
options_for_select Country.array_for_select

Related

How can I pass content to a parent template from a child template in Silverstripe

I want to pass some content from a elemental block up to a parent template (to change the page header) - Is this possible? I don't have any example code for what I'm trying because I don't have any idea how to implement it.
This would be similar to the content_for facility in Rails / ERB templates.
Parent:
Page Title <%= yield :title %>
Child Template:
<% content_for :title do %>
<b>, A simple page</b>
<% end %>
Is there anything like this or some other way to do this in SS templates?
Silverstripe Templates are a one-way street - you can't pass values upstream.
What you can do in this case is add a method to the page object to fetch the correct title from the block:
public function getTitleForTemplate()
{
return $this->MyBlock->PageTitle;
}
And then in your template you can simply call that method or as in this example treat it like a property:
Page Title $TitleForTemplate
If you want to use a template to render out this property (e.g. if you have different styling or markup for different blocks which belong to different pages) you can render the template in that method. There are many ways to do this but perhaps the most sensible would be to call renderWith() on the block object:
public function getTitleForTemplate()
{
return $this->MyBlock->renderWith(['type' => 'Includes', 'templates' => ['MyBlockTitle']);
}
and then have a template in your theme's templates/Includes directory called MyBlockTitle.ss which renders out the title with any styling and markup you need for it.
In other words: data belongs in models, the template is just there to display that data. So if you need some specific data to be displayed in the page template, the page object should have that data available.
Disclaimer: I haven't tested this code, it was done from memory. Some of the syntax especially for the second example may be slightly different in reality.
To expand on Guy's answer, to find the block i wanted to use to add to the page title and breadcrumbs, I used the ElementalArea and it's Elements.
public function getTitleForTemplate(){
$output = "" ;
foreach( $this->ElementalArea->Elements() as $element ){
if($element->ClassName == 'MySite\AwardsElement'){
if ( $element->Award() ){
$output .= "{$element->Award()->title } ";
}
}
}
return $output;
}

Is there a way to bind a variable number of queries?

I'm coding an app for managing shift work. The idea is pretty simple: the team is shared between groups. In those groups are specific shifts. I want to get something like that:
Group 1
- shift11
- shift12
- shift13
Group 2
- shift21
- shift22
- shift23
I already made a couple of tests, but nothing is really working as I would like it to: everything reactive, and dynamic.
I'm using vue.js, firestore (and vuefire between them).
I created a collection "shiftGroup" with documents (with auto IDs) having fields "name" and "order" (to rearrange the display order) and another collection "shift" with documents (still auto IDs) having fields "name", "order" (again to rearrange the display order, inside the group) and "group" (the ID of the corresponding shiftGroup.)
I had also tried with firestore.References of shifts in groups, that's when I was the closest to my goal, but then I was stuck when trying to sort shifts inside groups.
Anyway, with vuefire, I can easily bind shiftGroup like this:
{
data () {
return {
shiftGroup: [], // to initialize
}
},
firestore () {
return {
shiftGroup: db.collection('shiftGroup').orderBy('order'),
}
},
}
Then display the groups like this:
<ul>
<li v-for="(group, idx) in shiftGroup" :key="idx">{{group.name}}</li>
</ul>
So now time to add the shifts...
I thought I could get a reactive array of shifts for each of the groups, like that:
{
db.collection('shift').where('group', '==', group.id).orderBy('order').onSnapshot((querySnapshot) => {
this.shiftCollections[group.id] = [];
querySnapshot.forEach((doc) => {
this.shiftCollections[group.id].push(doc.data());
});
});
}
then I'd call the proper list like this:
<ul>
<li v-for="(group, idx) in shiftGroup" :key="idx">
{{group.name}}
<ul>
<li v-for="(shift, idx2) in shiftCollections[group.id]" :key="idx1+idx2">{{shift.name}}</li>
</ul>
</li>
</ul>
This is very bad code, and actually, the more I think about it, the more I think that it's just impossible to achieve.
Of course I thought of using programmatic binding like explained in the official doc:
this.$bind('documents', documents.where('creator', '==', this.id)).then(
But the first argument has to be a string whereas I need to work with dynamic data.
If anyone could suggest me a way to obtain what I described.
Thank you all very much
So I realize this is an old question, but it was in important use case for an app I am working on as well. That is, I would like to have an object with an arbitrary number of keys, each of which is bound to a Firestore document.
The solution I came up with is based off looking at the walkGet code in shared.ts. Basically, you use . notation when calling $bind. Each dot will reference a nested property. For example, binding to docs.123 will bind to docs['123']. So something along the lines of the following should work
export default {
name: "component",
data: function () {
return {
docs: {},
indices: [],
}
},
watch: {
indices: function (value) {
value.forEach(idx => this.$bind(`docs.${idx}`, db.doc(idx)))
}
}
}
In this example, the docs object has keys bound to Firestore documents and the reactivity works.
One issue that I'm trying to work through is whether you can also watch indices to get updates if any of the documents changes. Right now, I've observed that changes to the Firestore documents won't trigger a call to any watchers of indices. I presume this is related to Vue's reactivity, but I'm not sure.

Sectioning data in a ListView

Say you have a list of People incoming from your API.
[{content: 'John'},
{content: 'Tim'},
{content: 'Harry J. Epstein'}]
And you're looking to put people who are first-name-basis friends (John and Tim) under a section 'Friends' and people who are not (Harry J. Epstein) under 'Contacts'.
Tapping a friend selects them with a blue highlight, but tapping a 'contact' selects them with a red highlight.
Would the proper approach be to take the incoming data from the API, add a type: 'Friend', ... or type: 'Contact', ... around it, and section based on that type with separate a FriendItem and ContactItem class so I can split the highlighting function?
I've got a bunch of just basic ListView code that does this exact approach, but I'm basically looking for the easy way out, like Angulars ng-repeat equivalent.
So what's the React Native version of
var friends = api.getFriends()
var contacts = api.getContacts()
<div ng-repeat="friend in friends" ng-click="highlightFriend()"> ... </div>
<div ng-repeat="contact in contacts" ng-click="highlightContact()"> ... </div>
I'm struggling to understand how to split it. Do I need a FriendsPage, FriendsItem, and ContactsItem? Or put everything into one array in FriendsPage and use a FriendsItem that checks if it's a friend or contact and adds a function separately?
I feel like I'm slightly lost coming from MVC. I've got Redux running too, if there's an easy way using that.
Here is a nice example on how you can create section-dependent rows: https://github.com/spoeck/ListViewExample
The idea is basically to create the data blob properly, which is a bit tricky, and then in your renderRow callback, check the sectionID parameter:
_renderRow(rowData: any, sectionID: any, rowID: number) {
if (sectionID === this.data[0].section) {
return <MyFriends />
} else if (sectionID === this.data[1].section) {
return <MyContacts />
}else{
// ...
}
}
why don't you try SectionList
Use the new FlatList or SectionList component instead. Besides
simplifying the API, the new list components also have significant
performance enhancements, the main one being nearly constant memory
usage for any number of rows.

Laravel Queries / Controller Edits

So I am pretty new to Laravel, and I have spent the whole day fishing through various documentations but I am stuck on the way queries work within the actual application. Right now, I am trying to get some data in my database to display, and I looked at the query builder so that's where I am right now. I am also using a CRUD based admin panel for entry in the database. And since it is CRUD based, it has created the model and the controller already, so I am wondering if I need to edit any of those files to get this to work. Here is what the public function index() has right now (Using Laraadmin):
$module = Module::get('Events');
if(Module::hasAccess($module->id)) {
return View('la.events.index', [
'show_actions' => $this->show_action,
'listing_cols' => $this->listing_cols,
'module' => $module
]);
} else {
return redirect(config('laraadmin.adminRoute')."/");
}`
Obviously, I am trying to display some data from this Events table into my blade view. From what I was reading, I understood (or I thought) that it would be something similar to this:
foreach ($module as $module) {
echo $module->id;
}
But, I keep getting an error that whatever variable I pass in the loop is undefined, although I thought it was in the controller. Right now my model is just returning the view as well. Any help with this is greatly appreciated, or even just an explanation of the relationships with queries in Laravel. Thanks!
A few things to check when this happens:
Change module to a known array. This tests if your data array is set up correctly:
'module' => [ '1', '2', '3' ], // previously $module
Within the blade, now write a simple loop, such as:
#foreach ($module as $m)
{{ $m }}
#endforeach
If this version does work, then you know you have something up with your $module variable.
If the above still doesn't work, try to simplify your view request (temporarily):
view('foo', ['a' => 555]);
Then in foo.blade.php, simply have:
hello {{ a }}
EDIT
If all this seems to be correct, then the data being fetched is probably wrong (so its not a view issue). Try $module = Module::all();
It seems like you are returning a view that doesn't exist. If what you have now was correct, it would be looking for resources/views/la/events/index.blade.php Try replacing that return view line with this:
return view('events', [ ... rest of your variables ]);
And just a side note, on your foreach statement, it's probably best to use two separate variable names... so something like:
foreach ($module as $element) { ...

Reordering in ActiveAdmin

sometime we need to reorder our resources and acts_as_list is really useful for this task. My question is:
What is the best way to implement reordering of some resources in ActiveAdmin framework.
I know that there is no "best way" but I guess that all replies are welcome so people will be able to find all kind of answers for this kind of question.
I've written down one of the possible solutions myself, and it's using jquery with drag&drop, but isn't working with filters, scopes and sorting. Maybe there's a reason to dedicate separate view for drag&drop reordering, or maybe someone have done a different UI with checkboxes, buttons, etc…
Please share!
One of the solutions is described in Sortable lists with acts_as_list and ActiveAdmin. The solution is very nice, and all I can add from myself is a bit different serialization function and some more cosmetic stuff:
First of all, I've thought that that it'll be more efficient to move the desired resource into specified position instead of shifting all that are after it. Here is my updated update function:
$("#shows tbody").sortable({
update: function(event, ui){
var request
if (ui.item.next().length == 0)
request = {method: 'move_to_bottom', target: ui.item.find("span.show").data("id")}
else
request = {method: 'put_at_index', data: ui.item.next().find("span.show").data("id"), target: ui.item.find("span.show").data("id")}
$.ajax({
url: "/admin/shows/sort",
type: 'post',
headers: {
'X-Transaction': 'sort shows',
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
data: request,
complete: function(){
$(".paginated_collection").effect("highlight");
repaintTable();
}
});
}
});
As you can see, I either send a put_at_index method with the data what to put and on what item's index (that's actually the item below the one we have dragged) and if it was dragged to the bottom of the list, and there's nothing below it (after it) then I just send a move_to_bottom method with the data what to move to the bottom.
The sort action was also altered and now works like this:
collection_action :sort, :method => :post do
case params[:method]
when 'move_to_bottom'
Show.find(params[:target]).move_to_bottom
when 'put_at_index'
Show.find(params[:target]).insert_at(Show.find(params[:data]).position)
end
head 200
end
So it just uses insert_at and move_to_bottom methods of acts_as_list.
Also I've added a repaintTable so the odd and even rows have still different colors after the switch and I call it after the ajax request is complete.
function repaintTable()
{
$("#shows tr").removeClass('even odd');
$("#shows tr").filter(":odd").addClass('odd');
$("#shows tr").filter(":even").addClass('even');
}
The drawback is that it works bad with scopes, filters and sorting by some column.