Render view from another controller inside my view - cakephp

I don't know if this is possible, and i feel like its something i should not do, but i need it, so lets say:
I have a controller "A", this controller has a function that do some calculations and then there is the view that will render a div with some fields inside of it.
ok that is working, i can't touch that.
but now i have also the controller "B", this controller also has a function and a view, this view shows somethings and i need to show what is in the first case inside a div here (among the divs that are already present).
ps: the function on controller "A" needs two parameters.
There are some parts in this project that people used iframe and then .load() to show what is in A inside the iframe in another view, but i want to know if i can do this with cakephp itself using div.
cake version 2.4.9

Controllers aren't meant to call functions in other Controllers in Cake, but Cake has a few features to let you share functions between Views.
From what you imply you are doing here, likely you could stick the function into a View Cell:
namespace App\View\Cell;
use Cake\View\Cell;
class MyCell extends Cell
{
// Pass whatever data you need to, to the Cell
public function display($var1, $var2)
{
// Your calculations previously done in Controller A, do here instead, Ex.
$this->set('unread_count, $var1 - $var2);
}
}
Then put whatever div contents in the Cell template:
<!-- templates/cell/MyCell/display.php -->
<div class="notification-icon">
You have <?= $unread_count ?> unread messages.
</div>
Then plop the output into both Controller A & B's views, ex.:
<!-- In Controller A's & B's template, render where needed, passing the data needed: -->
<?= $this->cell('MyCell', [$var1, $var2]); ?>
This is only one option - depending on what exactly you're doing in that Controller, you could maybe stick the shared function into a Table, as a Custom Finder. You could stick the shared function in a plain old PHP Trait or Library.

Related

Sharing buttons and logic across views and controllers

I'm working on a single page app that behaves similar to a wizard. The user lands on page 1 and needs to save and continue to get to page 2. What's the best way to share those buttons between all the views? My form names are different so I currently have to duplicate these buttons so I can use logic like:
<div class="mySaveButton" ng-disabled="page1Form.$invalid"></div>
but then on page 2:
<div class="mySaveButton" ng-disabled="page2Form.$invalid"></div>
To further complicate matters, saving on page1 posts the data to a different address than page2. I have a navigation controller which is the parent and that needs to be handled as well.
So to summarize I need my buttons (Back, Save and Save and Continue) to do all of the following without having to duplicate the buttons across all views:
Check if the current form is valid
If it's valid, the data for that form needs to post to the correct endpoint for that form
Navigation needs to be notified so that it can update and/or take action
Essentially you need to re-use a template (/controller), and somehow pass options into it. You could probably do something with ngInclude and nesting controllers, but a way that is more friendly to more complex structure later is to create a custom directive. It depends a bit on your exact use-case, and exactly what you need to pass into it, but a simple version would be something like:
app.directive('formButtons', function() {
return {
restrict: 'E',
scope: {
'localBack':'&back',
'localSave':'&save',
'localSaveAndContinue':'&saveAndContinue'
},
templateUrl: 'buttons.html'
};
});
With a template of
<div>
<button ng-click="localBack()">Back</button>
<button ng-click="localSave()">Save</button>
<button ng-click="localSaveAndContinue()">Save & Continue</button>
</div>
The scope options with & each define a function on the directive's scope, that evaluates contents of the attribute on the <form-buttons> element of that name in the parent scope. You can then use the directive as follows
<form-buttons back="back(2)" save="save(2)" save-and-continue="saveAndContinue(2)"></form-buttons>
which will get replaced by the template of the directive, and the function save, back and saveAndContinue, which are defined on the parent scope, will get called when clicking on the buttons in the template, passing the appropriate step number which can be used to customise behaviour.
You can see a working example at http://plnkr.co/edit/fqXowQNYwjQWIbl6A5Vy?p=preview

Having a set of checkboxes map to a nested array

I am working on a SPA that pulls in customer data from one $resource call, and gets some generic preference data from another $resource call.
The preference data is sent as an array, which I want to use to populate a series of checkboxes, like so:
<div ng-repeat="pref in fieldMappings.mealPrefs">
<input type="checkbox"
id="pref_{{$index}}"
ng-model="customer.mealPrefs"
ng-true-value="{{pref.name}}" />
<label class="checkbox-label">{{pref.name}}</label>
</div>
When a user clicks one or more checkboxes, I want the values represented in that array of checkboxes to be mapped to an array nested inside a customer object, like so:
.controller( 'AppCtrl', function ( $scope, titleService, AccountDataService ) {
// this is actually loaded via $resource call in real app
$scope.customer = {
"name": "Bob",
"mealPrefs":["1", "3"]
};
// this is actually loaded via $resource call in real app
$scope.fieldMappings.mealPrefs = [
{'id':"1", 'name':"Meat"},
{'id':"2", 'name':"Veggies"},
{'id':"3", 'name':"Fruit"},
{'id':"4", 'name':"None"}
];
});
I have tried setting up ng-click events to kick off functions in the controller to manually handle the logic of filling the correct part of the customer object model, and $watches to do the same. While I have had some success there, I have around 2 dozen different checkbox groups that need to be handled somehow (the actual SPA is huge), and I would love to implement this functionality in a way that is very clean and repeatable, without duplicating lots of click handlers and setting up lots of $watches on temporary arrays of values. Anyone in the community already solved this in a way that they feel is pretty 'best practice'?
I apologize if this is a repeat - I've looked at about a dozen or more SO answers around angular checkboxes, and have not found one that is pulling values from one object model, and stuffing them in another. Any help would be appreciated.
On a side-note, I'm very new to plunkr (http://plnkr.co/edit/xDjkY3i0pI010Em0Fi1L?p=preview) - I tried setting up an example to make it easier for folks answer my question, but can't get that working. If anyone wants to weigh in on that, I'll set up a second question and I'll accept that answer as well! :)
Here is a JSFiddle I put together that shows what you want to do. http://jsfiddle.net/zargyle/t7kr8/
It uses a directive, and a copy of the object to display if changes were made.
I would use a directive for the checkbox. You can set the customer.mealPrefs from the directive. In the checkbox directive's link function, bind to the "change" event and call a function that iterates over the customer's mealPrefs array and either adds or removes the id of the checkbox that is being changed.
I took your code and wrote this example: http://plnkr.co/edit/nV4fQq?p=preview

Why ajax calls doesn't load the layout in CakePHP 2.3

I am wondering why if i use jquery $.load function or some pluging such as fancybox to load content dynamically on the site, the layoug is not loaded but only the view as if it were an element.
My $.load() calls a controller action as if it was a normal link, like:
$('#demo').load("http://"+ document.domain +"/tables/users/edit/", {input : data}, function(dat){
//whatever
});
This is not something I personally dislike, like this I avoid creating elements and calling them using $this->render('/Elements/xxxx', false); from my controllers.
I want to know if this is the proper way to work with or if it is some kind of cheat or bug of cakephp.
How should we treat this type of content which is not a proper "view" (as won't have a layout, headers...etc), but an "element" loaded dynamically? As a view? As an element?
Thanks.
Check /Layouts/ajax.ctp this is the layout that is rendered for ajax calls. Usually you don't want to have all the header and footer around the element you request when doing an ajax call.
Burzum is on the right track.
Your controller will load the default layout unless you tell it to use /Layouts/ajax.ctp. So in your edit function you'd want to switch layouts depending on how the function is being called. For example:
if($this->request->is('ajax')){
$this->layout = 'ajax';
}// else use controller default...or specify another layout to use here.

When using Marionette.js when should I call vent.trigger('app:show', Controller.layout)?

Of course the answer is "Use it when you want to show the app". Ok that is fair enough. But what about subviews? I am using Ted's example: https://github.com/t2k/backbone.marionette-RequireJS. That example only has one controller setup. I have six controllers which I copied the libraryController in Ted's example. Each example has the following code:
var _initializeLayout = function() {
console.log('initialize Start Layout...');
Controller.layout = new Layout();
Controller.layout.on("show", function() {
vent.trigger("startLayout:rendered");
});
vent.trigger('app:show', Controller.layout); <!-- is this needed for each?
};
So I have that code in each of my controllers. The StartLayout has two regions that have their own views that are the entry points to the InspectorController and the PlayerController.
Each of those controllers has:
vent.trigger('app:show', Controller.layout);
So it would seem to me that I may be calling 'app:show' more than needed. Once for every Controller that needs initializing.
Is this necessary? I can understand perhaps calling that when I'm dealing with direct child views of the app but if I'm deep into PlayerController and the app view isn't visible it seems like overkill.
Thanks,
Andrew
Try not to think of "calling" app:show. It's not a function, it's an event. An event can have an arbitrary number of subscriptions listening for it. In the case of this application, there is only one listener on that event:
vent.on('app:show', function(appView) {
app.content.show(appView);
});
In this case, it's telling the content region to display whatever view is included in the event as appView. So, if you want to replace the content region with your own view, you should trigger app:show with a parameter of whatever view you want the content region to display.
content is bound to a DOM element, and whenever you call content.show(someView), the contents of that DOM element will be replaced by whatever is generated by someView.render().el.
I would suggest reading up on Layouts, Regions, and Events.
Hope this helps.

Re-Rendering Handlebars partial from backbone view

I have a view, which holds handlebars template.
that template consist of another partial template.
that partial template holds a list of results, which i am using in different parts of my app.
anyhow, when trying to filter the results, i'd like to render only that part. meaning the backbone view should not render the whole view just the partial.
can it be done?
Yes, it's possible. The easiest way is to execute the whole template as you do when rendering the complete view, but only replace the the part you need in the view's el.
Something like:
template: Handlebars.compile(templateHtml),
render: function() {
//let's say your render looks something like this
this.$el.html(this.template(this.model.toJSON());
},
renderList: function() {
var html = this.template(this.model.toJSON());
var selector = "#list";
//replace only the contents of the #list element
this.$el.find(selector).replaceWith($(selector, html));
}
Depending on how dynamic your template is, you may have to call this.delegateEvents() after replacing the list for the view's events to work correctly.
Edit based on comments:
To clarify, the method I propose here does execute the view's main handlebars template again, but it doesn't render the whole view again.
Step by step:
Execute the Handlebars template function as you do in normal render.
var html = this.template(this.model.toJSON());
The variable html now contains a string of HTML markup. Nothing has yet been rendered.
Define a selector for the element, which you would like to re-render.
var selector = "#list";
Find the DOM element to replace. This presumes that you have already rendered the view once. Otherwise there will be no #list element within this.$el.
this.$el.find(selector)
Find the corresponding element in the templated html string, and replace the existing element with the new one:
.replaceWith($(selector, html));
This will only replace the #list element that's currently on the page. Anything outside #list will not be re-rendered or touched in any way.
The main reason I propose you do it this way instead of executing and rendering the partial template separately is that your view doesn't need to know anything about the implementation details of the template and the templating engine. All it needs to know that there is an element #list. I believe this is a cleaner solution, and keeps your template details separate from your view logic.

Resources