(01-21-2015, 03:38 PM)sneakyimp Wrote: (01-21-2015, 02:10 PM)mwhitney Wrote: It really sounds like you need to look into template and/or asset libraries, but here are my basic thoughts:
Thanks a LOT for your input. Any thoughts on libraries?
Unfortunately, no. I've been using the libraries included with Bonfire, but I'm extremely familiar with their limitations, and they would be difficult to extract from Bonfire for use as stand-alone libraries, anyway. There are adapters available to make most popular template and asset libraries work with CodeIgniter.
(01-21-2015, 03:38 PM)sneakyimp Wrote: (01-21-2015, 02:10 PM)mwhitney Wrote: Generally, if I need to access PHP functions/variables to generate values for my JavaScript, I create a view which contains a script tag in which I define some variables and echo the values from PHP. Then a JS file can access the variables and deal with them accordingly (or I can keep all of the associated script in the view). An assets library might offer more options for handling this type of issue.
Could you clarify? I'm guessing your HTML looks something like:
Code:
<script type="text/javascript" src="/some-codeigniter-method-that-output-JS-vars"></script>
And I'm further guessing that the CI method you are using for your src is aware of the appropriate language via cookie or something? Or perhaps there's a query string or url component that specifies the language?
Usually it's just a view that contains the JavaScript with PHP used to echo values. Then the view gets loaded into the page inside a script tag, so the resulting output just looks like inline JavaScript:
PHP Code:
<?php
// View code:
?>
$.subscribe('list-view/list-item/click', function(id) {
$('#content').load('<?php echo site_url(SITE_AREA . '/settings/analytics/edit'); ?>/' + id);
});
<?php
// resulting output might look like:
?>
<script>
$.subscribe('list-view/list-item/click', function(id) {
$('#content').load('http://sitearea/settings/analytics/edit/'+ id);
});
</script>
(01-21-2015, 03:38 PM)sneakyimp Wrote: (01-21-2015, 02:10 PM)mwhitney Wrote: For language-specific text, you'll most likely want to use language files and reference them using the lang() function (or $this->lang->line()).
We currently use such a scheme to fetch language strings. A view might have a line like this in it:
Code:
<li class="uk-active"><span><?php echo $this->lang->line('register_heading'); ?></span></li>
Does that sound about right?
The issue is not so much getting language strings, but finding an effective way to inject these language strings into JS and to inject that JS or a reference to a JS-outputting method into a <head></head> section in some view.
That looks about right. Generally, I wouldn't store text which needs to be localized in JS, but rather put them into the view and show/hide them via JS. However, as I mentioned before, you could always store them in a JS object or variable which is loaded via inline JS (loaded from a view which utilizes
$this->lang->line() to output the localized strings). You could then pass that object/variable into the function(s) which needs the localized strings. As long as the script is written with this in mind (e.g. with reasonable defaults if the values aren't passed), it shouldn't matter that the scripts are defined in separate locations.
Bonfire stores the user's language selection and sets the site's configured language (and the session language) when the current user is loaded:
PHP Code:
if (isset($this->current_user->language)) {
$this->config->set_item('language', $this->current_user->language);
$this->session->set_userdata('language', $this->current_user->language);
}
(01-21-2015, 03:38 PM)sneakyimp Wrote: (01-21-2015, 02:10 PM)mwhitney Wrote: Password requirements can be spelled out in the view, but further explanation is rarely needed for real-time feedback on password strength (though, if necessary, you could add an indicator of which check failed when returning the result and use that information to display the appropriate text).
Not sure I agree here. If a password is not valid for some reason, it can be a pain in the ass to figure out what is missing. Forcing your users to read that OWASP list of requirements is not exactly rocket science, but it will be a nuisance. I believe "X is an invalid character" is very helpful feedback. This is why I want language strings available in passwords.
Your script can return an indication that 'X is an invalid character' without returning that exact text. You could have it return a value which indicates which requirement failed and, in the case of an invalid character (or similar requirement), the character(s) which caused the error. The localized error message would already be loaded into the script or could be retrieved by calling a PHP method (via AJAX or post).
Most of the OWASP requirements are aimed at the person implementing the system.
For the end user, you just spell out the requirements:
- Password must be at least 10 characters long
- Password must contain at least 1 number.
- Password must contain at least 1 symbol (you may need to list the allowed symbols).
- Password must contain at least 1 capital letter.
- Password must contain at least 1 lowercase letter.
Specify any invalid characters or a maximum password length as well.
(01-21-2015, 03:38 PM)sneakyimp Wrote: (01-21-2015, 02:10 PM)mwhitney Wrote: For the rest, Bonfire uses a template library which simplifies a great deal of the management of potentially-complicated views, but you can easily create a much simpler library or setup some basic functionality in your base controller to handle the major issues.
I was thinking of some modification to my base controller that could load some view, merge it with some associative $data array of values, and return the resulting JS or HTML without putting the resulting merged view code into the output but I'm not really sure if this is feasible or how to do it. Do I need to write my own tool?
(01-21-2015, 02:10 PM)mwhitney Wrote: In most cases, your site is going to use a common template with a content region that changes for each action in your controllers (there can be exceptions, like AJAX methods, but those could just bypass the templating).
I've put some thought into AJAX and it would seem it's a bit more complicated than just bypassing the templating. For example, a base controller that requires users to be logged in may redirect the user to the login page, taking care to remember what page they requested that required login. This redirect behavior is not acceptable for AJAX operations, which should probably just return an error.
(01-21-2015, 02:10 PM)mwhitney Wrote: Instead of loading the same views in every controller/action, you can load them in a library or in a protected method in your base controller (named something like render(), for instance). You can define regions in the template for output of controller/action-specific assets (i.e. CSS and JS), and set them as you see fit
YES YES YES YES any thoughts on accomplishing this quickly/easily?
(01-21-2015, 02:10 PM)mwhitney Wrote: (though, in most cases, your template's head will usually be loaded before your view, so you usually can't load anything into the head from your main content region's view, so you are often limited to adding CSS from the controller in these situations, even if it's only telling the template which CSS files to load).
I don't ever load any views until after my processing is done and it seems pretty obvious to me that one must collect all the data needed for output before attempting to render any output by loading views.
Once you have a system like that in place, it's fairly easy to head down the rabbit hole and add more functionality.
I see you've already noticed the extra parameters when loading a view, which is a big part of templating, since you can keep loading views and storing them until you're ready to start output.
With AJAX, one of the biggest issues is handling tokens when you have certain security options enabled. Otherwise, you shouldn't have a lot of issues as long as your sessions are working properly. If the user has already logged in, an AJAX request isn't going to get redirected to login again unless their session was lost. However, if you wanted to handle it by returning an error, you could detect that it is an AJAX request in your authentication code and do so. Further, you would still probably want to bypass your templating in returning the error.
The Bonfire Template library is likely over-complicated for what you're going to want, but an untested skeleton version of it might look like this:
PHP Code:
<?php
class Template
{
protected static $blocks = array();
protected static $data = array();
protected static $layout;
protected static $view;
private static $ci;
/**
* CI constructor.
*/
public function __construct()
{
self::$ci =& get_instance();
self::init();
}
public static function init()
{
// Load some config values, if needed.
}
public static function setBlock($blockName, $viewName)
{
self::$blocks[$blockName] = $viewName;
}
public static function getData($varName)
{
if (isset(self::$data[$varName])) {
return self::$data[$varName];
}
return null;
}
public static function setData($varName, $value = null)
{
if (is_array($varName)) {
foreach ($varName as $key => $val) {
self::$data[$key] = $val;
}
} else {
self::$data[$varName] = $value;
}
}
public static function getLayout()
{
return self::$layout;
}
public static function setLayout($layout)
{
self::$layout = $layout;
}
public static function setView($view)
{
if (empty($view) || ! is_string($view)) {
return null;
}
self::$view = $view;
}
public static function block($blockName, $defaultView = '', $data = array())
{
$blockView = isset(self::$blocks[$blockName]) ? self::$blocks[$blockName] : $defaultView;
if (empty($blockView)) {
// No block set and no default block provided.
return null;
}
$output = self::loadView($blockView, $data);
echo $output;
}
public static function content()
{
return self::loadView(self::$view, null, self::$ci->router->class . '/' . self::$ci->router->method);
}
public static function render($layout = '')
{
$layout = empty($layout) ? self::$layout : $layout;
// If the current view has not been set, use the current controller/method.
$controller = self::$ci->router->class;
if (empty($self::$view)) {
self::$view = $controller . '/' . self::$ci->router->method;
}
if (self::$ci->input->is_ajax_request()) {
// Handle an AJAX request differently, if desired.
}
$output = self::loadView($layout, self::$data, $controller);
if (empty($output)) {
show_error("Unable to load layout: {$layout}");
}
self::$ci->output->set_output($output);
}
public static function loadView($view, $data = array(), $override = '')
{
if (empty($view)) {
return '';
}
if (empty($data)) {
$data = self::$data;
}
$output = '';
if (! empty($override)) {
$output = self::findFile($override, $data);
}
if (empty($output)) {
$output = self::findFile($view, $data);
}
return $output;
}
protected static function findFile($view, $data)
{
// Logic to find/load view...
$viewFile = "{$view}.php";
if (is_file($viewFile)) {
return self::$ci->load->view($view, $data, true);
}
return false;
}
}
With this, you would have a layout file with your content area set by calling
echo Template::content();. You could load a view into the template/layout by calling
Template::loadView() or you can set blocks by calling
Template::block('nameOfBlock', 'nameOfDefaultView', $data).
Then your controller can use
Template::setLayout() to set the layout,
Template::setBlock('nameOfBlock', 'nameOfView'); to change the view used for a block in your view/layout,
Template::setView('viewName'); to override the default view for the content area, and
Template::render() to output the whole thing.