I have the following model:
class Model_GrantMinimal extends Model_Table {
public $table='grant';
function init() {
parent::init();
$this->hasOne('User');
$this->getField('id')->hidden(true);
$this->getField('user_id')->hidden(true);
$this->addField('grant_number');
$this->addField('grant_name');
}
}
And inside the page I have the following code:
$grant=$this->add('Model_GrantMinimal');
$grant->load($id);
$user=$grant->ref('user_id');
$field = $grantForm->addField('Dropdown','Manager');
$field->setModel($user);
$field
->validateNotNull()
->add('Icon',null,'after_field')
->set('arrows-left3')
->addStyle('cursor','pointer')
->js('click',$grantForm->js()->reload())
;
And everything works almost perfectly - how do I make sure the Dropdown ($field in php) is linked to the overall form, i.e. when I change the value in the dropdown that value is passed into the $grantForm->onSubmit - and how do I ensure the the defaultValue (pre-selected value) of the dropdown is the User that is set by user_id inside GrantMinimal
I'm loving the framework so far - its really impressive and coming from the .NET framework where MVVM and MVC are so common, specially with the latest WPF related. It has been a treat compared to the old way of writing HTML/PHP, just taking a while to fully understand whats what.
Figured it out after a couple of hours of debug tracing:
class Model_GrantMinimal extends Model_Table {
public $table='grant';
function init() {
parent::init();
$this->hasOne('User');
$this->getField('id')->hidden(true);
$this->getField('user_id')->hidden(true);
$this->addField('grant_number');
$this->addField('grant_name');
$this->hasOne('User');
$this->getField('user_id')->caption('Manager')->hidden(false);
}
}
Related
In my previous protractor JS project (This new one I will do it with TS) I created one class for all my elements and another one for my functions, something like this:
specs
|_reportPage
|_lib
|_pageElements.js
|_pageFunctions.js
Then I was importing the files as necessary, in this way was easy to find the info since the element list was long.
So far all examples online for protractor TS projects are short pageObject files with a couple of elements and methods, but I would like to know how to correctly proceed when the page requires a lot of elements and functions/methods.
For example, lets say we have 5 specs under the same folder that test the same page and this page is full of fields and tables.
What would be the best practice here? create 1 pageobject for each spec, create one long class with all the elements and functions...?
Thanks for your time!
To Extend my answer you can add additional layer as a service which can execute several actions from the flow in different pages.
Code example:
export class E2EService {
mainPage: MainPage = new MainPage();
innerPage: InnerPage = new InnerPage();
doSomethingE2E() {
this.mainPage.headerPage.weDoSomething();
this.mainPage.contentPage.weDoSomething()
this.innerPage.somethingComplicated();
}
}
export class MainPage {
public readonly headerPage: HeaderPage;
public readonly contentPage: ContentPage;
}
export class InnerPage {
headerPage: InnerHeaderPage;
contentPage: InnerContentPage;
public somethingComplicated() {
this.headerPage.weDoSomething();
this.contentPage.weDoSomething();
}
}
export class ContentPage {
private readonly elements = {
// elements
};
public weDoSomething() {
// code
}
public getElements() {
return this.elements;
}
}
export class HeaderPage {
private readonly elements = {
btn1: element(by.id('')),
div: element(by.id('')),
h2: element(by.id(''))
};
public weDoSomething() {
// code
}
public getElements() {
return this.elements;
}
}
Based on Infern0's answer, I did dependency injection to the classes:
class HeaderElements {
foo = element(by.id("foo"));
//List goes on...
}
class HomePageElements {
foo = element(by.id("foo"));
//List goes on...
}
export class MainCommonElementsPage {
headerElements: HeaderElements;
homePageElements: HomePageElements;
constructor() {
this.headerElements = new HeaderElements();
this.homePageElements = new HomePageElements();
}
}
Best practices, even for large Page Objects is this:
Each page should only have 1 page object class. All of the tools needed to access that page should be located here. Think of your page object as an API.
Don't break the PO into different parts, especially for large pages. You'll eventually need to modify the PO to adjust for content changes. Would you rather change 1 file or 12? This also ensures that each of your e2e tests will remain functional after you update the PO.
I have one PO that handles a page with a lengthy form. The form has 12 controls and three buttons (cancel, reset, and submit). I have about 30 functions that deal with the form. I don't like having more than 1-2 methods in my test, so if it gets more complicated, I add to the PO.
Im using xamarin forms with freshmvvm framework.
I would like to know how I can skip using xaml, and just access binding data from code behind in c#.
Are there any code samples that could help?
Although this goes against the principles of MVVM there is of course a way to do it.
Without a MVVM framework you would just create a ViewModel by hand and set the BindingContext (documentation) yourself. The 'only' thing (in regard to this) a MVVM framework does for you is set that binding up automatically so you're not bothered with writing the same code over and over again.
So, imagine you have this ViewModel, note I user PageModel to match the FreshMvvm naming:
// SamplePageModel.cs
public class SamplePageModel
{
public string Foo { get; set; } = "Bar";
}
Now in my Page, I set the BindingContext like this:
// SamplePage.cs
// ... Skipped code, just constructor here:
public SamplePage()
{
InitializeComponent();
BindingContext = new SamplePageModel();
}
Now you can bind to any property of SamplePageModel.
FreshMvvm does this part automagically.
If, for whatever reason, you would like to access the ViewModel/PageModel directly, just do the reverse. Somewhere in your Page or code-behind you can do:
// ... Some code here
var pageModel = BindingContext as SamplePageModel;
// ... More code here
Now if pageModel isn't null there you have your data-bound and filled PageModel!
I found Gerald's answer helpful, but I found that you need to override this event in your page vs doing the as in the constructor:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
var pageModel = BindingContext as YourFreshMVVMPageModel;
// Modify the page based on the pageModel
}
The PageModel construction seems to take place after the page Constructor, and this Event seems to fire at the right time and still make the page do what you want.
I am trying to use the autocomplete addon in agile toolkit (I am still very much new to this, but it seems to be very well suited for my needs). Autocomplete Basic works, but when I use Plus and press the plus-button I get an error connected to no model set. In the Plus source the self-model should be used - but I don't understand how I should set the model of the autocomplete form.
This is the important part of the stack trace, I think:
Frontend_page_form: Form->setModel(Null)
Frontend_createquestions_form_question_id:
autocomplete\Form_Field_Plus->autocomplete{closure}(Object(Page))
This is my model:
class Model_QuestionInCollection extends Model_Table {
public $entity_code='questionincollection';
function init(){
parent::init();
$this->hasOne('Question')->display(array('form'=>'autocomplete/Plus'));
This is the code:
$form=$this->add('Form');
$form->setModel('QuestionInCollection');
--- EDIT
I ended up changing the model in autocomplete, and now it works, showing that something is wrong in the original "$self->model" - but of course it cannot be generalized. I did some extra changes (to make the new record show up in the autocomplete-field), so Autocomplete/Plus now is like this:
<?php
namespace autocomplete;
class Form_Field_Plus extends Form_Field_Basic
{
function init()
{
parent::init();
$self = $this;
$f = $this->other_field;
// Add buttonset to name field
$bs = $f->afterField()->add('ButtonSet');
// Add button - open dialog for adding new element
$bs->add('Button')
->set('+')
->add('VirtualPage')
->bindEvent('Add New Record', 'click')
->set(function($page)use($self) {
$model=$this->add('Model_Question');
$form = $page->add('Form');
$form->setModel($model); //Was: $self->model
//Would be nice if it worked...: $form->getElement($model->title_field)->set($self->other_field->js()->val());
if ($form->isSubmitted()) {
$form->update();
$js = array();
$js[] = $self->js()->val($form->model[$model->id_field]);
$js[] = $self->other_field->js()->val($form->model[$model->title_field]);
$form->js(null, $js)->univ()->closeDialog()->execute();
}
});
}
}
Here is an example on using autocomplete: http://codepad.demo.agiletech.ie/interactive-views/autocomplete
You can manually create a form and link the field with Model.
If that works fine, you can move on to trying and get your own example working. It seems OK, and should work in theory.
I want to populate a dropdown from a table 'accountgroups' which has 2 columns id and name. The first row of dropdown should be blank or '--Select Account Group--' when first loaded. Once the user selects the item (i.e. display field 'name' and value field 'id') I want to get id and name values when form is submited.
You'll need to create a model for your table first:
class Model_AccountGroups extends Model_Table {
public $table='accountgroups';
function init(){
parent::init();
$this->addField('name');
}
}
$form->addField('dropdown','account_id')->setModel('AccountGroups');
if($form->isSubmitted()){
$form->js()->univ()->alert('Selected ID='.$form->get('account_id'))->execute();
}
Probably (your post have an year and a half) you had resolved your issue, but I want anyway contribute with "my 2 cents" for this issue. I looked around (documentation, forums, etc) and neither found another solution to the same problem. My solution (tested and working) is this (please is is not efficient enough let me know):
>>>>>>>>>>>>>>>>>>>>> ON MODEL
class Model_Offices extends Model_Table {
public $entity_code='Offices';
function init(){
parent::init();
$this->addField('ID')->ReadOnly(True);
$this->addField('OfficeName');
}
function GetAll() {
$r=array();
$AllOffices = $this->SetModel('Offices');
foreach($AllOffices as $OneOffice) {
$r[$OneOffice['ID']]=$OneOffice['OfficeName'];
}
return $r;
}
}
>>>>>>>>>>>>>>>>>>>> ON PAGE
$m=$this->Add('Model_Offices');
$form->addField('dropdown','Office')->SetValueList($m->GetAll()) ;
Mack
I am looking for some help and I hope that some good soul out there will be able to give me a hint :)
I am building a new application by using MVVM Light. In this application, when a View is created, it instantiates the corresponding ViewModel by using the MEF import.
Here is some code:
public partial class ContractEditorView : Window
{
public ContractEditorView ()
{
InitializeComponent();
CompositionInitializer.SatisfyImports(this);
}
[Import(ViewModelTypes.ContractEditorViewModel)]
public object ViewModel
{
set
{
DataContext = value;
}
}
}
And here is the export for the ViewModel:
[PartCreationPolicy(CreationPolicy.NonShared)]
[Export(ViewModelTypes.ContractEditorViewModel)]
public class ContractEditorViewModel: ViewModelBase
{
public ContractEditorViewModel()
{
_contract = new Models.Contract();
}
}
Now, this works if I want to open a new window in order to create a new contract... or in other words, it is perfect if I don't need to pass the ID of an existing contract.
However let's suppose I want to use the same View in order to edit an existing contract. In this case I would add a new constructor to the same View, which accepts either a model ID or a model object.
"Unfortunately" the ViewModel is created always in the same way:
[Import(ViewModelTypes.ContractEditorViewModel)]
public object ViewModel
{
set
{
DataContext = value;
}
}
As far as I know, this invokes the standard/no-parameters constructor of the corresponding ViewModel at composition-time.
So what I would like to know is how to differentiate this behavior? How can I call a specific constructor during composition time? Or how can I pass some parameters during the Import?
I really apologize if this question sounds silly, but I have only recently started to use MEF!
Thanks in advance,
Cheers,
Gianluca.
You CAN do this. Check out the Messenger implementation in MVVM-Light. You can pass a NotificationMessage(Of Integer) to send the right ID to the view model. The view model has to register for that type of message, and load it when a message is sent.
MEF Imports by default only have a parameterless constructor.