I have a controller (Controller_Product) that extends Controller_Template.
In the Controller_Product I have some actions (create, edit, etc.) where I need the template to be rendered, but some actions (ex. save, delete) have to return a json object, so I don't need the template to be rendered.
How can I solve this problem?
I can set the $this->auto_render to FALSE in my save or delete action, but the template will be created in this case too, even if will be no rendered. I think this is not very elegant to load a template when I don't actually need it.
Any suggestions?
Something along these lines perhaps:
public function before()
{
if (in_array($this->request->action(), array('save', 'delete')))
{
$this->auto_render = FALSE;
}
parent::before();
}
[edit]
A better approach might be to check for an ajax request:
public function before()
{
if ($this->request->is_ajax())
{
$this->auto_render = FALSE;
}
parent::before();
}
Related
I'm changing the value of a property in my controller and the helper fails to recompute them.
Sample code here:
My template looks like,
{{#if (my-helper info)}}
<span>Warning</span>
{{/if}}
In my controller,
changeAction: function() {
let that = this,
info = that.get("info");
set(info, "showWarning", true);
}
my helper,
import { helper as buildHelper } from '#ember/component/helper';
export default buildHelper(function(params) {
let that = this,
info = that.get("info");
if(info.showWarning ) {
return true;
} else {
return false
}
});
I see several issues with your code:
The template helper seems to get an object as it's first and only position param: {{my-helper info}} while info is { showWarning: true }. A template helper does recompute if the value passed it changes but not if a property of that value changes. A quick fix would be {{my-helper info.showWarning}}.
In your template helper your are trying to access the property on it's this context. As far as I know that's not supported. As you are using a positional param and it's the first one, it's available as first entry inparams array. So your template helper should look like:
export default buildHelper(function([info]) {
if(info.showWarning ) {
return true;
} else {
return false
}
});
What version of Ember are you using? If it's >= 3.1 you don't need to use this.get() in your controller. If you are using Ember < 3.1 you need to use info.get() also in template helper.
But as described before I would not recommend passing an object to a template helper as it's only updated if the object itself is replaced. Changing a property of it is not enough. You might be able to do so using Class-based Helpers but I wouldn't recommend to do so as it's error prune.
I currently use a {{link-to}} helper that was written by someone else to explicitly state the query params to pass to the next route and strip out others that are not stated. It looks like this:
//link-to example
{{#link-to 'route' (explicit-query-params fromDate=thisDate toDate=thisDate)} Link Text {{/link-to}}
//the helper
import {helper} from '#ember/component/helper';
import Object from '#ember/object';
import {assign} from '#ember/polyfills';
export function explicitQueryParams(params, hash) {
let values = assign({}, hash);
values._explicitQueryParams = true;
return Object.create({
isQueryParams: true,
values,
});
}
export default helper(explicitQueryParams);
// supporting method in router.js
const Router = EmberRouter.extend({
_hydrateUnsuppliedQueryParams(state, queryParams) {
if (queryParams._explicitQueryParams) {
delete queryParams._explicitQueryParams;
return queryParams;
}
return this._super(state, queryParams);
},
});
I've recently had a use case where I need to apply the same logic to a transitionTo() that is being used to redirect users from a route based on their access:
beforeModel() {
if (auth) {
this.transitionTo('route')
} else {
this.transitionTo('access-denied-route')
}
},
I am really struggling to see how I can refactor what I have in the handlebars helper to a re-usable function in the transitionTo() segment. I'm even unsure if transitionTo() forwards the same arguments as {{link-to}} or if I will have to fetch the queryParams somehow from a different location.
Any insight would be greatly appreciated.
Well first off, tapping into private methods like _hydrateUnsuppliedQueryParams is risky. It will make upgrading more difficult. Most people use resetController to clear stick query params. You could also explicitly clear the default values by passing empty values on the transition.
But, ill bite because this can be fun to figure out :) Check this ember-twiddle that does what you're wanting.
If you work from the beginning in the transitionTo case, we can see that in the router.js implementation:
transitionTo(...args) {
let queryParams;
let arg = args[0];
if (resemblesURL(arg)) {
return this._doURLTransition('transitionTo', arg);
}
let possibleQueryParams = args[args.length - 1];
if (possibleQueryParams && possibleQueryParams.hasOwnProperty('queryParams')) {
queryParams = args.pop().queryParams;
} else {
queryParams = {};
}
let targetRouteName = args.shift();
return this._doTransition(targetRouteName, args, queryParams);
}
So, if the last argument is an object with a query params obj, that's going directly into _doTransition, which ultimately calls:
_prepareQueryParams(targetRouteName, models, queryParams, _fromRouterService) {
let state = calculatePostTransitionState(this, targetRouteName, models);
this._hydrateUnsuppliedQueryParams(state, queryParams, _fromRouterService);
this._serializeQueryParams(state.handlerInfos, queryParams);
if (!_fromRouterService) {
this._pruneDefaultQueryParamValues(state.handlerInfos, queryParams);
}
}
which has the _hydrateUnsuppliedQueryParams function. So, to make this all work, you can't share the function directly from the helper you've created. Rather, just add _explicitQueryParams: true to your query params. Job done :)
The link-to version is different. The query params use
let queryParams = get(this, 'queryParams.values');
since the link-to component can take a variable number of dynamic segments and there needs to be some way to distinguish between the passed dynamic segments, a passed model, and query params.
partial void Query1_PreprocessQuery(string Filter1, ref IQueryable<Type> query)
{
//how can I loop through the query and add data to a custom list
}
Generally speaking, the _PreprocessQuery method is for defining the contents of the query and not doing anything with those contents (which would be Post processing). So a simple method might read:
partial void Query1_PreprocessQuery(string Filter1, ref IQueryable<Type> query)
{
query = query.Where(x => x.FilterColumn == Filter1);
}
This is happening on the server side so even if you did intercept the results, I think it would be tricky to get any list you created back to the client side.
Once the query results have been passed to the client screen, you can then loop through the query and use the contents however you like, for example using Screen Property methods like Query1_Loaded or Collection methods like Query1_Changed depending on what you're trying to achieve. Untested code, but something like this:
partial void Query1_Loaded(bool succeeded)
{
// Loop through the rows on the screen ...
foreach (IEntityObject rowData in this.Query1)
{
// Reference individual values like this ...
string FilterResult = rowData.Details.Properties["FilterColumn"].Value.ToString()
}
}
}
I spent a little while trying to figure out how to implement a sub menu bar and eventually decided all I really want is a little helper method that appends my template to the current view instead of an actual view helper:
//To use in any action requiring the sub navbar to be displayed
protected function addSubNav(ViewModel $view) {
$subNavView = new ViewModel();
$subNavView->setTemplate('helpdesk/helpdesk/subNav');
$view->addChild($subNavView, 'subNav');
return $view;
}
But when I call it in a method like this $this->subNav in my template is null:
public function indexAction() {
//return new ViewModel();
$this->addSubNav(new ViewModel());
}
When doing $this->subNav in index.phtml is NULL, why is that?
addSubNav() should be returning the view which I appended a template to.
You don't return your view model a the end of your action
public function indexAction() {
return $this->addSubNav(new ViewModel());
}
I have a collection of related classes, call them
class Manager {
private:
std::vector<Setting> m_settings;
}
class Setting {
private:
std::vector<Action> m_actions;
}
class Action {
private:
Keybind m_keybind;
}
class Keybind {
public:
UpdateKeybind;
private:
TYPE keybind;
}
As you can see from the pseudo-C++ code, Settings have actions, and actions have exactly one key binding. Now, as a user of my application you want to update the Keybind potentially, yes?
I currently have buttons in a keybind-type dialog associated with each action, so the action can handle updating it's own keybind.
My Problem:
How do I ensure that the Keybinding isn't bound to another object?
Possible solutions:
Move UpdateKeybind itself to the Manager class, then have Manager query all the settings.
Have a parent pointer in Action/Setting/Keybind so the Action can query the manager for updated keybind.
Have the Action query other Actions (not great conceptually as far as I can tell).
What I need from you:
What is the most rigorous approach, in terms of maintainability, speed, ease of understanding, and OOP appropriateness, to implementing checking if a Keybind is already found, whether out of my suggested solutions or something else entirely. I have already tried number 1 -- it works, but I feel like it could be better, ya dig?
I was unable to find similar questions on StackOverflow, but if you do I'd love to see them!
Any other pro tips, things to improve are helpful.
Just like #Amardeep says, you can try creating a class managing the mapping between actions and keybindings. Following is an example. It will automatically remove the keybind to the action if the there is new binding to that keybind.
class Manager {
private:
std::vector<Setting*> m_settings;
KeybindPool *m_keybindPool;
};
class Setting {
private:
std::vector<Action*> m_actions;
};
class Action {
public:
void Bind(Keybind* keybind) {
m_manager->m_keybindPool->Update(this, keybind)
}
Keybind* getKeybind() const {
return m_manager->m_keybindPool->getKeybind(this);
}
private:
Manager *m_manager;
};
class KeybindPool {
public:
void Update(Action* action, Keybind* keybind) {
if (m_keybindActionMap.find(keybind) != m_keybindActionMap.end()) {
m_actionKeybindMap.erase(action);
}
m_keybindActionMap[keybind] = action;
m_actionKeybindMap[action] = keybind;
}
Keybind* getKeybind(Action* action) {
return m_actionKeybindMap[action];
}
private:
map<Keybind*, Action*> m_keybindActionMap;
map<Action*, Keybind*> m_actionKeybindMap;
};
class Keybind {
private:
TYPE keybind;
}
Since you have an exactly 1:1 relationship between key bindings and actions, you could start with a pool of key binding objects and draw down from the pool as you configure actions. So when offering up available keys for configuration, any keys already bound would not be in the available pool.