• 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Passing language strings to Javascript, view organization help

#1
I'm working on a user registration page for a site that supports multiple languages. According to the OWASP Recommendations for authentication, it's considered 'best practice' to provide users with real time feedback as to whether the password they are choosing is valid. This gets complicated for a few reasons:

1) I need to provide language strings to my Javascript
I need to feed these language strings into my view which will in turn make them available to my Javascript. I would prefer to have these strings defined somewhere in the <head></head> region of my HTML output where I would also like to define my JS that attaches onChange() events to my form inputs when I'm sure they have been defined.

2) I really want to avoid defining JS or HTML in my controllers.
CI's view implementation doesn't really lend itself all that well to nesting of complex interface elements into some other element. One typically loads views in sequence:
Code:
// a controller method
public function foo() {
 // collect $data here somehow
 $this->load->view("header"); // contains opening <body> tag, probably <head>, also HTML to define site-wide menu
 $this->load->view("some_page", $data); // data for the current page. E.g., product info or a user profile or an article or something
 $this->load->view("footer"); // maybe some google analytics stuff or something, closing </body> tag, etc.
}
If I want to get JS defined between the <head>...<head> tags in the header view, I have to either break up my header template, drastically complicating my view organization, or I must define my JS in a $variable in my controller -- this is bad form as I understand things. Shouldn't be writing PHP to generate HTML or JS strings. The sequential loading of views in this way also doesn't really seem to jibe all that well with the nested/hierarchical structure of HTML. I.e., we are often loading one view to open an element (e.g., <html>, <body>, possibly a large <div> or <form> and another, separate, view to close the HTML element with its </html> counterpart.

3) I'd prefer not to define a bunch of redundant Javascript files that are intermingled with language-specific data
While I can certainly imagine creating a bunch of JS files that define only prompts for each language so that I can separate language out from the regex logic to check one's password, it seems like poor organization to me to have these prompts intermingled with HTML and/or JS. This sort of intermingling typically makes translation work difficult because your translators need JS and/or HTML chops and awareness to avoid breaking things.

Bottom line is that it seems optimal to get my language strings from my database or from some data store and NOT have it intermingled with a bunch of HTML or JS. It also seems like I'll need to inject complex JS/HTML into my views or risk drastically complicating my view organization (and controllers) if I have to redesign these views to break them up further. The most maddening part about this will be situations where I have some opening <HTML_ELEMENT> tag in one view with its corresponding </HTML_ELEMENT> tag in another.

Can anyone offer a shrewd approach to managing views that eases this issue? I've looked at the Output class but can't really tell from the docs how I might use it to load a view and capture its contents after they are merged with data.  I can also imagine an approach using DOM but that seems like it might be complicated too.

Any help or suggestions would be greatly appreciated!
Reply

#2
You can capture the output of a view by setting the 3rd parameter of $this->load->view(), to TRUE.

$captured_view = $this->load->view('view_file', $data, TRUE); //doesn't output to browser, but returns the rendered view
Reply

#3
It really sounds like you need to look into template and/or asset libraries, but here are my basic thoughts:

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.

For language-specific text, you'll most likely want to use language files and reference them using the lang() function (or $this->lang->line()).

For password strength specifically, the script Bonfire uses simply defines a "status" on the password field of invalid/weak/good/strong which is indicated through a class applied to the field (and, optionally, an image). 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).

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.

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). 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 (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).

Once you have a system like that in place, it's fairly easy to head down the rabbit hole and add more functionality.
Reply

#4
I wouldn't mess with javascript for getting the "languages" at all. You can retrieve those using ajax when you submit your form (I'm assuming your're using ajax to do this to get "real time feedback"), or having the returned view already rendered using the appropriate language as my earlier post showed how to retrieve a view file instead of sending it to the browser.
Reply

#5
(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?

(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?


(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.

(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.

(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.
[/quote]
Reply

#6
(01-21-2015, 02:08 PM)CroNiX Wrote: You can capture the output of a view by setting the 3rd parameter of $this->load->view(), to TRUE.

$captured_view = $this->load->view('view_file', $data, TRUE); //doesn't output to browser, but returns the rendered view
SWEET. I only just saw this. Had been looking into writing my own function but was concerned about how it might interrupt output.
Reply

#7
(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::$viewnullself::$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($layoutself::$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$datatrue);
 
       }

 
       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.
Reply

#8
Hello,

I would like to start over with your original question(s).

1) I need to provide language strings to my Javascript --> Okay, that should not be a problem

2) I really want to avoid defining JS or HTML in my controllers. --> Of course, it does not belong there

3) I'd prefer not to define a bunch of redundant Javascript files that are intermingled with language-specific data --> Mmm, okay there we have got a challenge.

The most simple solution for your problem would be to have different Javascript language files like this:

resources.en.js
resources.de.js
resources.fr.js


The content of a resource file could look like this:

var resource = {

Cancel: "Abbrechen",
Caution: "Achtung",
Confirm: "Bestätigen",
Confirmation: "Bestätigung"

}


When the language is German,

echo resource.Cancel

will print 'Abbrechen'. The only thing you have to do is to include the right Javascript language file based on the user's language.

There are two approaches:

1. You load the Javascript file in the view in the controller;
2. You load the Javascript file in a view in the view

I would go for the first approach, because you do not want to have any logic in your view. The decision which Javascript file to load has to be made in the controller.

The questions is of course where? It depends where you want to include your Javascript files, all in the header or all at the end of the body. Let's say you want to include the at the end of the body, your code could look like this.

$data = array(); //fill with whatever data you want
$this->load->view("header", $data);

$data = array(); //fill with whatever data you want
$data['language'] = $this->current_user->language;
$this->load->view("some_page", $data);

$data = array(); //fill with whatever data you want
$this->load->view("footer", $data);


You simple pass the user's language to the view. (No need to set it in the session.)

At the end of the view you simply have a line like this:

<script type="text/javascript" src="resources.<?php echo $language; ?>.js"></script>

followed by all the other required Javascript files:

<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.3.min.js"></script>

That will simply do.... If you would choose this approach, you can use these Javascript language files also for your PHP code. So all the language strings required by your application are stored in these files. (You just have to write some simple code which reads the Javascript file and places the data in a PHP array.)

BUT what if you do not want that? What if the language strings come from a database? I can think of a few solutions. One of them would be AJAX. Mmmm, seems a bit overkill for something this simple. Two more reasonable solutions would be:

1. Read the language strings from the database, pass them to the view and parse them as inline code;
2. Read the language strings from the database and place them in a Javascript resource (similar as described before);

The first approach seems the most efficient one, but actually isn't. The reading of the database has to be done with every page request (unless you store the strings in the session, which you do not want). Reading from the database costs time. Parsing the strings also costs time.  

With the second approach you simply check for the existence of the Javascript file. If it is present, just use it and otherwise create it, only once. The only thing you have to think of is to remove the Javascript files after making changes to the resource strings in the database.

Is this better than just having the language strings in Javascript files (maybe also used as PHP language resource files)? It depends on your motives for having the language strings in the database. Is there a good reason? Why would you?

Another question. Are the language strings you use in PHP different from the ones in Javascript? If there is little overlap, I would go for different resource files for PHP and Javascript. In that case you do not have to write PHP code to read the files and put the language strings in a PHP array. Just include the file in the right place based on the user's language.

You end up with double resource files. (So what?)

resources.en.js
resources.de.js
resources.de.js
resources.en.php
resources.de.php
resources.de.php


Of course a file like resources.de.php would look like this:

<?php
$resource = array(
'Cancel' => 'Abbrechen',
'Caution' => 'Achtung',
'Confirm' => 'Bestätigen',
'Confirmation' => 'Bestätigung');
?>


Anyhow, try to keep things simple. My € 0.02
Reply


Digg   Delicious   Reddit   Facebook   Twitter   StumbleUpon  


  Theme © 2014 iAndrew  
Powered By MyBB, © 2002-2020 MyBB Group.