Binding in code to property with operation without special ValueConverter - mvvmcross

In axml I can Bind like this
local:MvxBind="Visibility Status==0, Converter=Visibility"
but when I'm trying to do it in code:
set.Bind(_imgStatus).For(c => c.Hidden).To(vm => vm.Status == 0).WithConversion("Visibility");
or
set.Bind(_imgStatus).For(c => c.Hidden).To(vm => vm.Status.Equals(0)).WithConversion("Visibility");
I get error
Property expression must be of the form 'x => x.SomeProperty.SomeOtherProperty' or 'x => x.SomeCollection[0].Property' (System.ArgumentException)
I'm aware that I can write my own ValueConverter, but I'm interested if there's a way to surpass that, since there's obviously a way to write it in axml.

MvvmCross doesn't include expressions like To(vm => vm.Status == 0) in the fluent syntax. This is partly because they are hard to code... and partly because their coding often requires runtime compilation (which iOS won't allow)
The only way around this is to use the string format in iOS - e.g. like in https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/blob/master/N-35-Tibet/BindMe.Touch/Views/FirstView.cs#L63
So your sample might become something like:
set.Bind(_imgStatus).For(c => c.Hidden).To("Visibility(Status == 0)");
or even (in this case):
set.Bind(_imgStatus).For(c => c.Hidden).To("Status != 0");
There are also some described fluent methods to allow you to pass in the entire binding string - e.g. something like:
set.Bind(_imgStatus).FullyDescribed("Hidden Visibility(Status != 0)");

Related

How to compare two fields/columns in a condition?

I am having a hard time trying to figure out how to get a sub-query working.
Imagine I have:
$schools
->select($this->Schools)
->select([
'pupilcount' => $this->Pupils
->find()
->select([
$this->Pupils->find()->func()->count('*')
])
->where([
'Pupils.school_id' => 'Schools.id',
]),
The problem I am experiencing (I think) is that Schools.id is always 0 and so the count is returned as 0. I can pull out the Pupils join and it shows Pupils there.
I tried changing my code to add a:
->select(['SCID' => 'Schools.id'])
and reference that in the sub-query but doesn't work, it will always return 0 for the pupilcount.
What am I doing wrong here?
Whenever encountering query problems, check what queries are actually being generated (for example using DebugKit). Unless being an expression object, the right hand side of a condition will always be bound as a parameter, ie you're comparing against a string literal:
Pupils.school_id = 'Schools.id'
Generally for proper auto quoting compatibility, column names should be identifier expressions. While the left hand side will automatically be handled properly, the right hand side would require to be handled manually.
In your specific case you could easily utilize QueryExpression::equalFields(), which is ment for exactly what you're trying to do, comparing fields/columns:
->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) {
return $exp->equalFields('Pupils.school_id', 'Schools.id');
})
It's also possible to create identifier expressions manually by simply instantiating them:
->where([
'Pupils.school_id' => new \Cake\Database\Expression\IdentifierExpression('Schools.id')
])
or as of CakePHP 3.6 via the Query::identifier() method:
->where([
'Pupils.school_id' => $query->identifier('Schools.id')
])
And finally you could also always pass a single string value, which is basically inserted into the query as raw SQL, however in that case the identifiers will not be subject to automatic identifier quoting:
->where([
'Pupils.school_id = Schools.id'
])
See also
Cookbook > Database Access & ORM > Query Builder > Advanced Conditions
API > \Cake\Database\Expression\QueryExpression::equalFields()
API > \Cake\Database\Expression\IdentifierExpression

Cakephp 3: Modifying Results from the database

In my database there is a content table and when fetching data from this table I would like to append field url to the results, which is based on slug field which is contained in the table. Anyway, I have seen a way to do this in the previous versions of cakephp using behavior for the model of this table and then modifying results in afterFind callback in the behavior class. But in version 3 there is no afterFind callback, and they recommend using mapReduce() method instead in the manual, but this method is poorly explained in the manual and I cant figure out how to achieve this using mapReduce().
After little bit of research I realized that the best way to append the url field field to find results is using formatResults method, So this is what I did in my finders:
$query->formatResults(function (\Cake\Datasource\ResultSetInterface $results) {
return $results->map(function ($row) {
$row['url'] = array(
'controller' => 'content',
'action' => 'view',
$row['slug'],
$row['content_type']['alias']
);
return $row;
});
});

Castle Windsor: conditional component registration when scanning an assembly

Am using Castle Windsor to register multiple components that share one common interface at the top chain, using the following:
container.Register(
Types.FromAssemblyNamed("MyProject.MyAssembly")
.BasedOn<IService>()
.WithServiceAllInterfaces()
.Configure(c => c.LifeStyle.HybridPerWebRequestTransient())
)
Among all the components inside MyAssembly, I have the following:
public interface IHandler : IService
public class MessageHandler : IHandler
public class CachedMessageHandler : IHandler
During the registration above, is there a way to pick one of the two components (MessageHandler or CachedMessageHandler) based on some conditional value?
Yes there is.
.BasedOn<IService>()
is shorthand for
.Pick().If(t => typeof(IService).IsAssignableFrom(t))
Knowing that, you have the possibility to do powerful things. Just to give you an idea what you can do:
1.
Possibly this is what you wanted. Pick one of the two components based on some conditional value:
var shouldUseCache = true; //get this from configuration
//later in container.Register
.Pick().If(t =>
{
if (shouldUseCache)
{
return typeof (IService).IsAssignableFrom(t) && typeof (CachedMessageHandler) == t;
}
return typeof (IService).IsAssignableFrom(t) && typeof (MessageHandler) == t;
})
Be warned though:
once the component is registered, it will be hard/impossible/antipattern to remove that registration from the container
if you make "conditional registration", it may be tricky for developers to debug injections later. Depending on your situation you may want to create HandlerProvider which will have explicit methods like: .GetCached or .GetTransient
2.
You can select all types which implement IService but exclude those which also implement IHandler:
.Pick().If(t => typeof(IService).IsAssignableFrom(t)
&& !typeof(IHandler).IsAssignableFrom(t))
3.
You can do naming conventions, for example only register all types which implement IHandler and ends with SuperHandler:
.Pick().If(t => typeof(IHandler).IsAssignableFrom(t)
&& t.Name.EndsWith("SuperHandler"))

Use a widget in a statically-called method

Normally a widget is used by calling CController::widget() on an instance of CController, typically $this in a view.
But if I'm writing a static method, a helper, say, then I don't have access to an instance of CController. So how do I use a widget?
Let's say further that this helper method is invoked in the eval()’ed expression in a CDataColumn's value property. That poor expression has almost no context at all. How should the helper use a widget?
EDIT: Code example
As requested, a view example:
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider' => $model->search(),
'columns' => array(
array(
'name' => 'attrName',
'value' => '--USE WIDGET HERE--',
),
)
));
This answer doesn't answer the question in general but in the specific case—how to access the controller and use a widget in the context of the evaluated expression of CDataColumn::$value—you can use this:
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider' => $model->search(),
'columns' => array(
array(
'name' => 'attrName',
'value' => function ($data, $row, $column) {
$controller = $column->grid->owner;
$controller->widget(/* ... etc ... */);
},
),
)
));
The trick was discovering that CDataColumn::renderDataCellContent() uses CComponent::evaluateExpression(), which injects the component instance into the callback as the last parameter. In this case that omponent is the CDataColumn, which references the controller as shown.
I don't like writing PHP expressions as string literals so I'm pleased to find this option.
A comment on http://www.yiiframework.com/doc/api/1.1/CDataColumn#value-detail shows another way to us a widget in a column value that I haven't tried.
This one is working solution for calling widgets in static methods in Yii
Yii::app()->controller->widget('widget');
There's no direct way to call a widget out of controller because you shouldn't do so. It's all about MVC. Widgets are only needed and/or useful in views, and views are only accessed via controllers. That's the theory.
I guess you're approaching the problem mistakenly. A proper, MVC-friendly way to do what your're trying to do involves using renderPartial(). You know: you a have certain content and you want to decorate it (in your case you want to imbibe it inside a widget, right?) before displaying it to final user; so, from the view, you call renderPartial(). It will send your data to a file where it will properly decorated. renderPartial() returns the content properly formatted and now you can display it in the view.
Unfortunately, in your particular case, you're working with grid view (right?) and, at least from my point of view, it makes the things a bit harder. In order to decorate content for a CGridColumn-subclass element (like CDataColumn), you need to override the renderDataCellContent() method. Check it out here: http://www.yiiframework.com/doc/api/1.1/CDataColumn#renderDataCellContent-detail

Zend Forms and Ext.grid.Panel

I am working for a company who use tabulated html/JS interfaces. These are home grown (real honest to god s) with query events attached to each cell. For the old usage they were suitable, but the interactions required between rows and cells are becoming much more complex on the client side. Specifically they want both server and client side validation.
To facilitate this, the devs I report to are super keen on Zend_Forms, and insist that to use a framework like ExtJS, they don't want to have to write back end and front end code twice (please ignore that if it's all home grown they'll have to do this anyway).
So with that in mind, I'm trying to leverage Zend_Form decorators to create Ext.grid.Panel column defintions. For this, I would need to use decorators to export an array (and then json it using the ViewHelper), or render a JSON string directly.
So this would be something like:
$dateElement = new Zend_Form_Element_Text('startDate', array(
'label' => 'Start Date',
'validators' => array(
new Zend_Validate_Date()
)
));
echo (string)$dateElement;
would output:
{ text: 'Start Date', dataIndex:'startDate', xtype:'datecolumn'}
or (obviously not with string cast, but maybe with ->toArray() or something):
array( 'text' => 'Start Date', 'dataIndex' => 'startDate', 'xtype' => 'datecolumn')
I think if I could get it to this stage, I could get what I need out of it.
Has anyone here tried to do anything similiar to this (getting a JSON/XML/other markups output, rather than HTML from Zend_Forms using Decorators) or if they could point me to any resources?
I think I have a solution...
Make a decorator similar to this:
class My_Form_JSON_Decorator extends Zend_Form_Decorator_Abstract{
protected $xtype;
protected $dataIndex;
public function __construct($dataIndex,$xtype){
$this->xtype=$xtype;
$this->dataIndex=$dataIndex;
}
public function render($content){
$element=$this->getElement();
$label=$element->getLabel
//if you need errors here too do the same with $element->getMessages();
return 'array ("text"=>"'.$label.'","dataIndex"=>"'.$this->dataIndex.'","datecolumn"=>"'.$this->xtype.'")';
}
}
Then, on the form, use something similar to this:
$dateElement = new Zend_Form_Element_Text('startDate', array(
'label' => 'Start Date',
'validators' => array(
new Zend_Validate_Date()
)
$dateElement->setDecorators(array(
new My_Form_JSON_Decorator("startDate","datecolumn");
));
And finally, on the View, you should have this:
{
Date: <?php echo $this->form->startDate; ?>,
}
I didn't tried the code above but, I did it with a similar code I used once when I needed to change Decorators of a Form.
It could not be all correct but, I think that it shows you a way of doing that.
Good work =)