Lets say i have multiple public functions in a single controller, i am trying to fine a way to display 1 option per page for example, first page user selects a schoolYear then hit submit, next page the user selects a school and hit submit, 3rd page the user selects a term and with a final page of all the selected fields being shown and a delete button. i am starting to test my page now and am wondering what is the best way to accomplish this?
class SchoolYearsController extends AppController
{
public function index()
{
}
public function chooseYear()
{
$schoolYearsTable = $this->loadModel('SchoolYears');
$schoolYears = $schoolYearsTable->find()->order(['SchoolYears.endYear'=>'desc']);
$this->set('schoolYears',$schoolYears);
$this->request->getSession()->write('App.schoolYear',$this->request->getData('schoolYear'));
return $this->redirect(['controller'=>'SchoolYears', 'action'=>'chooseSchool']);
}
public function chooseSchool()
{
$schoolListsTable = $this->loadModel('Schools');
$schoolYear = $this->session->read('App.schoolYear');
$schools = $schoolListsTable->find()
->where([
'fiscalYear' => $schoolYear,
'districtCode' => 'MA',
])
;
$this->set('schools',$schools);
return $this->redirect(['controller'=>'SchoolYears', 'action'=>'chooseTerm']);
}
public function chooseTerm()
{
$chosenTerm = $this->request->getData();
}
}
Related
My entity
/**
* #ORM\ManyToOne(targetEntity="Estat", inversedBy="temes")
*/
private $estat;
public function setEstat(\Ncd\ForumBundle\Entity\Estat $estat = null)
{
$this->estat = $estat;
return $this;
}
My admin
protected function configureListFields(ListMapper $listMapper)
{
//$estats=$this->getEstatsPossibles()->toArray();
$estats=array();
foreach($this->getEstatsPossibles() as $estat)
{
$estats[$estat->getId()]=$estat->getNom();
}
$listMapper
->add('estat', 'choice',['editable' => true,'choices'=> $estats])
I'd like to make estat field editable in the list grid. Doing it on this way I get make it editable, a combobox appears but when I chose an option I get an exception because setEstat function of my entity does not recive an Estat entity, but a string (the array's key).
Trying
->add('estat', 'many_to_one',['editable' => true,'choices'=> $estats])
Only appears a link to the entity without any possibility to change.
Is it possible?
Waiting for a better and cleaner solution I'v solved this injecting an entityManager in my entity following the solution of this answer:
Get entityManager inside an Entity
Then, in my entity I've changed setEstat function:
public function setEstat( $estat = null)
{
if (is_object($estat) && get_class($estat)=='Ncd\ForumBundle\Entity\Estat')
{
$this->estat=$estat;
} else {
$estat_o=$this->em->getRepository('Ncd\ForumBundle\Entity\Estat')->find((int)$estat);
$this->estat = $estat_o;
}
return $this;
}
My question is in regards to the "Compelling Example" given for ReactiveUI where as a person types in a search bar, the search occurs asynchronously. Suppose though I wanted to provide my user with a way to refresh the current search results. I could just ask them to backspace in the search bar and retype their last character. However, they are asking for a "Refresh" button because it's not obvious to them how to refresh the current results.
I can't think of how to do this within the context of the example:
public class TheViewModel : ReactiveObject
{
private string query;
private readonly ObservableAsPropertyHelper<List<string>> matches;
public TheViewModel()
{
var searchEngine = this.ObservableForProperty(input => input.Query)
.Value()
.DistinctUntilChanged()
.Throttle(TimeSpan.FromMilliseconds(800))
.Where(query => !string.IsNullOrWhiteSpace(query) && query.Length > 1);
var search = searchEngine.SelectMany(TheSearchService.DoSearchAsync);
var latestResults =
searchEngine.CombineLatest(search, (latestQuery, latestSearch) => latestSearch.Query != latestQuery ? null : latestSearch.Matches)
.Where(result => result != null);
matches = latestResults.ToProperty(this, result => result.Matches);
}
public string Query
{
get
{
return query;
}
set
{
this.RaiseAndSetIfChanged(ref query, value);
}
}
public List<string> Matches
{
get
{
return matches.Value;
}
}
}
Does anyone have any suggestions on how I could capture a command from a button and re-execute the existing search without clearing out their current search text?
You can merge the existing observable of Query changes with a new observable that returns the current Query when the refresh button is pressed.
First a command for the refresh button:
public ReactiveCommand<Unit, String> Refresh { get; private set; }
Then you create the command and assign it, and create a merged observable of the two observables:
Refresh = ReactiveCommand.Create<Unit, String>(() => Query);
var searchEngine = Observable.Merge(
this.ObservableForProperty(input => input.Query).Value().DistinctUntilChanged(),
Refresh)
.Throttle(TimeSpan.FromMilliseconds(800))
.Where(query => !string.IsNullOrWhiteSpace(query) && query.Length > 1);
The rest can stay unchanged.
I have a form with about 20 fields. I have a ListBox that is populated with Customers from the Model when the page loads. Once the user picks one of those Customers from the ListBox, I want to post to the Controller the selected Customer, get customer's info, return it to same view, and populate some of the fields with the Customer's info.
Here is what I am trying now, but it might not be the best way. Also, the onclick gets called on page load, which causes an infinite loop.
View - CreateUser
#Html.ListBoxFor(x => x.Id,
Model.Customers.Select(
x => (new SelectListItem {
Text = x.Name,
Value = x.Value.ToString(),
Selected = x.IsSelected})).OrderBy(x => x.Text),
htmlAttributes new {
onclick = #Html.Action("GetCustomerInfo", "Customer", Model)
})
Controller - Customer
[ChildActionOnly]
public ActionResult GetCustomerInfo(CustomerModel Model)
{
// populate model with customer info
return View("CreateUser", Model);
}
Also, if there is a better way for this solution, I would love to hear any ideas. I am trying to avoid loading all Customers and then just using Angular to change the text fields based on selected Customer, since there is going to be over 1,000 customers and it would be slow to initially load all of them.
#Html.Action() is razor code and is parsed on the server so GetCustomerInfo() is called before the page is sent to the client. The fact its associated with the onclick event of a control is irrelevant. The infinite loop is because the view returned by GetCustomerInfo is the same view your trying to render - it contains the same #Html.Action() so GetCustomerInfo is called again, which returns a view with the same #Html.Action() so GetCustomerInfo is called again and so on.
You can use ajax to update the DOM with the selected customers details.
View models
public class SelectCustomerVM
{
[Display(Name="Select customer to display details")]
public int? CustomerID { get; set; }
public SelectList CustomerList { get; set; }
}
public class CustomerVM
{
public int ID { get; set; }
public string Name { get; set; }
// other properties of customer
}
Controller
public ActionResult Index()
{
SelectCustomerVM model = new SelectCustomerVM();
model.CustomerList = new SelectList(db.Customers, "ID", "Name");
return View(model);
}
public ActionResult Details(int ID)
{
CustomerVM model = new CustomerVM();
// get customer from database and map properties to CustomerVM
return PartialView(model);
}
Index.cshtml
#model SelectCustomerVM
#Html.LabelFor(m => m.CustomerID)
#Html.DropDownListFor(m => m.CustomerID, Model.CustomerList, "--Please select--")
<div id=customerdetails></div>
<script type="text/javascript">
$('#CustomerID').change(function() {
var customerID = $(this).val();
if(customerID) {
$.get('#Url.Action("Details", "Customer")', { ID: customerID }, function(data) {
$('#customerdetails').html(data);
});
} else {
$('#customerdetails').empty();
}
});
</script>
GetCustomer.cshtml (partial view)
#model CustomerVM
#DisplayFor(m => m.ID)
#DisplayFor(m => m.Name)
....
Some best practices to note. Don't pollute your view with code to construct SelectList's - that's the responsibility of the controller; and use Unobtrusive javascript - don't mix content and behavior.
Im currently making a Yii2 RESTful system with AngularJs.
In my database i've got several columns that i want to be able to return when doing a particular call from a certain point in my system.
The problem i'm having is how do i return only a handful of fields eg(id, title and stub) from the restful call in another part of my system so that it ignores other fields in the table.
I would ideally like it to work in a similar way to how a Models rules work with scenarios in yii.
There are two methods, I think:
1. use params
// returns all fields as declared in fields()
http://localhost/users
// only returns field id and email, provided they are declared in fields()
http://localhost/users?fields=id,email
// returns all fields in fields() and field profile if it is in extraFields()
http://localhost/users?expand=profile
// only returns field id, email and profile, provided they are in fields() and extraFields()
http://localhost/users?fields=id,email&expand=profile
2. overriding model's fields()
// explicitly list every field, best used when you want to make sure the changes
// in your DB table or model attributes do not cause your field changes (to keep API backward compatibility).
public function fields()
{
return [
// field name is the same as the attribute name
'id',
// field name is "email", the corresponding attribute name is "email_address"
'email' => 'email_address',
// field name is "name", its value is defined by a PHP callback
'name' => function () {
return $this->first_name . ' ' . $this->last_name;
},
];
}
// filter out some fields, best used when you want to inherit the parent implementation
// and blacklist some sensitive fields.
public function fields()
{
$fields = parent::fields();
// remove fields that contain sensitive information
unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
return $fields;
}
more detail, refer to https://github.com/yiisoft/yii2/blob/master/docs/guide/rest-resources.md
You may use scenarios method inside your model for this, but you will have to extend a bit toArray method in order to make it work properly:
public function scenarios()
{
return array_merge(parent::scenarios(), [
'simple_info' => [
'email',
'name',
],
'login' => [
'id',
'email',
'name',
'auth_token',
],
]);
}
public function toArray(array $fields = array(), array $expand = array(), $recursive = true)
{
$scenarios = $this->scenarios();
$scenario = $this->getScenario();
if (!empty($scenarios[$scenario])) {
$data = parent::toArray($fields, $expand, $recursive);
return array_intersect_key($data, array_flip($scenarios[$scenario]));
}
return parent::toArray($fields, $expand, $recursive);
}
After this you may simply do something like this:
$model = new LoginForm();
if ($model->load(Yii::$app->request->post(), '') && $model->login()) {
$user = $model->getUser();
// Lets change scenario to login in order to get `auth_token` for authorization
$user->setScenario('login');
$user->generateAuthKey();
$user->save(FALSE);
return $user;
} else {
return $model;
}
As a side note (expanding on the answer from #Ganiks), if you are manually returning the list of Models, you will need to return them as a DataProvider (rather than simply as an array of Models) for the fields parameter to have an effect.
For example, if you do something like this...
class UserController extends yii\rest\Controller
{
public function actionIndex()
{
return User::find()->all(); // Not what you want
}
// ...
}
... then the fields parameter will not have the desired effect. However, if you instead do this...
class UserController extends yii\rest\Controller
{
public function actionIndex()
{
return new ActiveDataProvider([
'query' => User::find(),
'pagination' => false,
]);
}
// ...
}
... then the returned fields will only be those you specified in the fields parameter.
I know that's kinda simple and lame question, but still.
I have a Form which should not show all Model fields, but only some of them. That's why I can't use Form->setModel($m), because that'll automatically add all fields into Form.
So I add Model into page, then add form into page and then use form->importFields like this:
$m = $p->add('Model_Example');
$f = $p->add('Form');
//$f->setModel($m); // can't use this because that'll import all model fields
$f->importFields($m,array('id','description'));
$f->addSubmit('Save');
What I don't understand in this situation is - how to save this data in database, because $f->update() in onSubmit event will not work. Basically nothing I tried will work because Form have no associated Model (with setModel).
How about this way?
$your_form->setModel($model,array('name','email','age'));
I have solution for mixed form. Add custom form fields in form init and manipulate with them by hooks ('afterLoad','beforeSave')
In this case you can use setModel() method
$form->setModel('Some_Model',array('title','description'));
class Form_AddTask extends Form {
function init(){
parent::init();
$this->r=$this->addField('autocomplete/basic','contact');
$this->r->setModel('ContactEntity_My');
}
function setModel($model,$actual_fields=undefined){
parent::setModel($model,$actual_fields);
$this->model->addHook('afterLoad',array($this,'setContactId'));
$this->model->addHook('beforeSave',array($this,'setContactEntityId'));
return $this->model;
}
// set saved value for editing
function setContactId() {
$this->r->set($this->model->get('contact_entity_id'));
}
function setContactEntityId() {
$this->model->set('contact_entity_id',$this->get('contact'));
}
}
There is a hook 'validate' as well in Form_Basic::submitted(), so you can add
$this->addHook('validate',array($this,'validateCustomData'));
and validate your data in Form::validateCustomData()
Why not set the fields to hidden in the model?
I.e.:
class Model_Example extends Model_Table {
public $table='assessment';
function init() {
parent::init();
$grant->addField('hidden_field')->hidden(true);
}
}
And then:
$m = $p->add('Model_Example');
$f = $p->add('Form');
$f->setModel($m);