Doing some investigation for using dust.js, and I was wondering is there a way from preventing bad data to be rendered.
Template
Hello {name}! You have {count} new messages
Context
{
"name": "Mick",
"count": Math.PI
}
Yields, this result:
Hello Mick! You have 3.141592653589793 new messages
In this example, is there a way to escape the Math.PI, so that we can bail out and not print 3.14..
You, as the developer, have to decide what is 'bad data' and what is an acceptable alternative.
Then you must either transform it in code (eg. the node.js building the page) before it reaches dust.js, or write a helper to render whatever you want with appropriate fallback. For instance, if you want to render integers, and display some custom fallback text otherwise, you might use a helper something like this:
Create an integerOrElse function, and save it in a file, eg.
local-dust-helpers.js:
// this extends dustjs-helpers (which must therefore be in package.json)
var dust = require('dustjs-helpers');
dust.helpers.integerOrElse = function (chunk, ctx, bodies, params) {
// tap function resolves variables in params
var value = dust.helpers.tap(params.value, chunk, ctx),
fallback = dust.helpers.tap(params.fallback, chunk, ctx) || '';
// define a fallback for the fallback :) ----------------^^^^^
// for more brevity, you could do this in one line with a ternary operator
if (!isNaN(value) && parseInt(value) == value) {
return chunk.write(value);
} else {
return chunk.write(fallback);
}
}
Then require() it in your app, replacing where you would have called the vanilla dust.js:
app.js
...
var dust = require('./local-dust-helpers');
...
You can then use it just like a native dust.js directive:
template.dust
Hello {name}!
You have {#integerOrElse value='{count}' fallback='some' /} new messages
Related
I want to know how I can access the parsed model of my program. I have a validation check written in xtend which accepts a rule A as it parameter. however I want to search the entire parsed tree and make sure that any other reference to this specific instance of A follows certain specifications.
#Check
def checkActionBelongsToAssociatedRole(ActionDsc act){
var pRole = act.parentRole
var rs = new ResourceSetImpl()
//DONT KNOW IF THIS IS RIGHT
var resource = rs.getResource(URI.createURI("./model/generated/Protocol.ecore"), true)
for(r:resource.allContents.toIterable.filter(typeof(RoleDec))){
if(r.name == pRole.name){
//DO SOMETHING
}
}
}
In the generator file that I have I already get the Resource object as a parameter.
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
//Generate code
}
How can I do the same thing for my validator. Thank you in advance!
act.eResource() allows to access the resource that contains the action.
I have a page with links. These links all end in the same way. For example www.site.com/fruit/apples, www.site.com/fruit/bananas, www.site.com/fruit/oranges, etc. I want all these links to call the same Dart app and have the app do some processing and then redirect you wherever you need to go (the bananas page vs. the oranges page). This way, I avoid having an actual HTML file for every single fruit. I can instead have a single landing template that gets populated with variable fruit data.
The part I'm hung up on is passing the url into the Dart app so it can do the handling. I understand main() cannot receive arguments, so what's another way?
You can use the route package to handle the URL's for you.
For example:
import 'package:route/client.dart';
final fruitUrl = new UrlPattern(r'/fruit/(\w+)');
main() {
var router = new Router()
..addHandler(fruitUrl, showFruit)
..listen();
}
void showFruit(String path) {
var fruit = fruitUrl.parse(req.path)[0];
// Display the page according to the fruit type
}
If you don't need to handle actual routes, and you just want to handle any query parameters passed of the form ?fruit=apple you don't have to use the routes package and can instead manually parse the URL:
Map params = {};
// If arguments were provided, decode them into params map
if(window.location.search.length > 1) {
// Break all arguments into form: fruit=apple
List<String> queryPairs = window.location.search.substring(1).split('&');
for(String queryPair in queryPairs) {
// Add arguments into params map: key=fruit, value=apple
List<String> queryPairList = queryPair.split('=');
params[queryPairList[0]] = queryPairList[1];
}
}
// Handle the proper action based on the fruit
switch(params['fruit']) {
case 'apple':
// ...
break;
// ...
case 'orange':
// ...
break;
}
I'm trying to modify the Todos Examples to get a better understanding of the framework.
I'm trying to modify the todosController to add a 'completed' computed property that returns all the completed todos. On top of this, I'm trying to get the 'areAllCompleted' property to update.
I have this code, which does not update 'areAllCompleted' when 'completed' has changed.
TodosThree.todosController = SC.ArrayController.create({
completed: function(){
if(this.get('content')){
return this.get('content').find(
SC.Query.local(
TodosThree.Todo,
'isCompleted = true'
)
);
}
else {
return [];
}
}.property('content').cacheable(),
areAllCompleted: function (k, v) {
console.log('get');
if (v !== undefined) {
this.setEach('isCompleted', v);
}
return this.getPath('completed.length') === this.get('length');
# This .property definition doesn't work with .*completed.length .. ?
}.property('length','.*completed.length')
});
However, if I change the code slightly to add a binding, it works:
TodosThree.todosController = SC.ArrayController.create({
completed: function(){
if(this.get('content')){
return this.get('content').find(
SC.Query.local(
TodosThree.Todo,
'isCompleted = true'
)
);
}
else {
return [];
}
}.property('content').cacheable(),
# For some reason, this binding works ...
completedLengthBinding: SC.Binding.oneWay('.*completed.length'),
areAllCompleted: function (k, v) {
console.log('get');
if (v !== undefined) {
this.setEach('isCompleted', v);
}
return this.getPath('completed.length') === this.get('length');
# If I reference the binding that references completed, this now works ...
}.property('length','completedLength')
});
Why does this subtle difference suddenly make it work?
Thanks.
When you are using .property() method, the parameters are expected to be direct properties of the object; so, it doesn't expand upon the property path that you are passing (.*completed.length). When you setup the binding, you are basically telling SproutCore that you want the path (.*completed.length) bound to a property of the object, which is why the second one works; since it has become a simple property.
Since you are setting both of these based off of the completion, another way that you could do it is by using a single function with .observes() which does follow property paths, but that is a bit complex. Following is how I would probably handle this:
/*
* Note that using a store.find will auto-update when new todos are pushed
* into the store, so there is no need to reset this every time.
*/
completed: TodosThree.store.find(
SC.Query.local('TodosThree.Todo', 'isCompleted = true')
),
/*
* Go ahead and grab the length so we can use it as a dependent property below
*/
completedLengthBinding: '*completed.length',
/*
* Create the property function
*/
allAreCompleted: function(key, value) {
if (value !== undefined) {
this.setEach('isCompleted', value);
}
return this.everyProperty('isCompleted');
}.property('completed', 'completedLength')
A couple of things to note: since you're wanting to call allAreCompleted() and pass a value, you DO want this as a property, not just an observer. You could technically do it with a function that acts as both an observer and a property updater, but I think this is more clear.
Also, note the use of the everyProperty() method, which will iterate over each todo and ensure that the passed property is true for all todos.
Hope this helps! Please ask if you need clarification on anything :-D
CakePHP URL query parameters are not done in a standard fashion e.g. the params are /param1:value1/param2:value2 instead of ?param1=value1¶m2=value2
This means that the javascript location.search does not return a value.
There is a getQueryParams JQuery plugin that does what I want using location.search
I have had to modify this to use
var pairs = location.pathname.split('/');
instead of
var pairs = location.search.substring(1).split('&');
However this now includes everything except the host in the variable pairs. So I have to check for a ':' to see if it is a parameter.
This works - but is there a better (more Cake like) way of doing it? I don't want to improve on the JQuery plugin (e.g. Regex), I want to find a better way to integrate the plugin with CakePHP.
Upddate: I've removed the rest of the JQuery code as I'm happy with the jquery code, my issue is with fitting it more with cake
Is there some 'Cake like' way of removing the path to your app, the model and the controller from location.pathname so that you end up what you would normally get from location.search?
Since you're searching for a particular parameter, you can use a regular expression:
$.getQueryParam = function (param) {
var re = new RegExp(param+':([^\/]+)');
var matches = location.pathname.match(re);
if (matches.length) {
return matches[1];
}
return undefined;
}
So it appears there isn't a better way of doing it. Here is the javascript for reference:
// jQuery getQueryParam Plugin 1.0.1 (20100429)
// By John Terenzio | http://plugins.jquery.com/project/getqueryparam | MIT License
// Modified by ICC to work with cakephp
(function ($) {
// jQuery method, this will work like PHP's $_GET[]
$.getQueryParam = function (param) {
// get the pairs of params fist
// we can't use the javascript 'location.search' because the cakephp URL doesn't use standard URL params
// e.g. the params are /param1:value1/param2:value2 instead of ?param1=value1¶m2=value2
var pairs = location.pathname.split('/');
// now iterate each pair
for (var i = 0; i < pairs.length; i++) {
// cakephp query params all contain ':'
if (pairs[i].indexOf(':') > 0) {
var params = pairs[i].split(':');
if (params[0] == param) {
// if the param doesn't have a value, like ?photos&videos, then return an empty srting
return params[1] || '';
}
}
}
//otherwise return undefined to signify that the param does not exist
return undefined;
};
})(jQuery);
I want to give admin the option to change the URL identifier of MyCustomModule from backend.
E.g.: www.mydomain.com/identifier
What I did is the following:
In etc/system.xml
<identifier translate="label">
<label>SELF URL Identifier</label>
<frontend_type>text</frontend_type>
**<backend_model>press/config_identifier</backend_model>** // edited after answer
<sort_order>1</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment>(eg: domain.com/identifier)</comment>
</identifier>
In helper/data.php
public function getUrl($identifier = null)
{
if (is_null($identifier)) {
$url = Mage::getUrl('').self::getListIdentifier();
} else {
//$url = Mage::getUrl(self::getListIdentifier()).$identifier;
**$url = Mage::getUrl(self::getListIdentifier(), array('identifier' => $identifier,'_use_rewrites'=>true)); //edited
}**
return $url;
}
after that i created a model file identifier.php :
class FME_Press_Model_Config_Identifier extends Mage_Core_Model_Config_Data
{
protected function _afterSave()
{
if ($this->isValueChanged()) {
$path = $this->getValue();
// for each $store and $id combination...
Mage::getModel('core/url_rewrite')
->loadByIdPath('press/'.$store.'/'.$identifier)
->setRequestPath($path.'/'.$identifier)
->save();
}
}
}
in config.xml i wrote this:
<events>
<controller_front_init_routers>
<observers>
<press>
<type>singleton</type>
<class>FME_Pres_Controller_Router</class>
<method>initControllerRouters</method>
</press>
</observers>
</controller_front_init_routers>
</events>
and also this is present in my file, m not sure whether it is relevant :
<adminhtml>
<args>
<modules>
<FME_Press_Override before="Mage_Adminhtml">FME_Press_Override_Admin</FME_Press_Override>
</modules>
</args>
</adminhtml>
NOTE: I had been told to make some changes in Controller/Router.php but I don't know what changes to make.
If you want I can add that code also?
Now, what else should I do?
I feel changing the application's router is entirely the wrong approach to take. It is messy and can be easily broken if another module overrode it for a similar purpose. The clean way is with URL rewrites.
You want it to be alterable so you cannot use a fixed XML based rewrite. Instead let's look at the built in rewrite system.
First in your module's etc/config.xml file set up a normal controller.
<frontend>
<routers>
<MyCustomModule>
<use>standard</use>
<args>
<module>Example_MyCustomModule</module>
<frontName>customlist</frontName>
</args>
</MyCustomModule>
</routers>
</frontend>
Here the front name used is customlist, that will always work and shouldn't conflict with any other front name, the rewritten name shall be in addition to this. Now whenever you generate an URL (perhaps in a helper function) you do so to this apparently fixed front name.
$url = Mage::getUrl('customlist', array(
'id' => $id, // 'id' will get used in the "target path" later
'_use_rewrites' => true
));
Note that the variable identifier ($id) is passed to the getUrl function rather than simply appending to it's result. If the function returns an URL with a query (&) or fragment (#) the identifier could have been appended to the wrong part.
The next step is to create rewrite records for every possible combination of identifier and store. You probably have a finite number of lists so this is possible, perhaps identifiers are particular to stores so only need to be defined once each. Either loop through all your lists in an installer script or have each list create rewrites when it is saved.
$path = Mage::getStoreConfig('custom/config/identifier', $storeId);
// Change 'custom/config/identifier' to match the path used in system.xml
$rewrite = Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$store.'/'.$id);
if ($rewrite->getId()) {
// A rewrite already exists, you might want to skip creating another
continue;
}
Mage::getModel('core/url_rewrite')
->setStoreId($storeId)
->setIsSystem(true) // set to false to allow admin to edit directly
->setOptions('RP') // Redirect Permanent 301
->setIdPath('customlist/'$storeId.'/'.$id) // should never change
->setTargetPath('customlist/index/index/id/'.$id) // what gets used
->setRequestPath($path.'/'.$id) // the path used in the browser
->save();
So now if the admin sets the URL path to be "foo/bar" and requests the page "www.mydomain.com/foo/bar/3" it will be rewritten to "customlist/index/index/id/3" and the method Example_MyCustomModule_IndexController::indexAction() will be called. The file containing that will of course be app/code/local/Example/MyCustomModule/controllers/IndexController.php and the 3 value is retrieved there:
public function indexAction()
{
$id = $this->getRequest()->getParam('id'); // 'id' was specified in getUrl()
// use $id here...
}
It should work by now but what if a list is removed? The rewrites need to be updated for each store. Models have a _beforeDelete method, override it for your list objects.
protected function _beforeDelete()
{
Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$storeId.'/'.$this->getId())
->delete();
return parent::_beforeDelete();
}
Similarly they need to be updated to match changes in configuration.
etc/system.xml
<identifier translate="label">
<label>SELF URL Identifier</label>
<frontend_type>text</frontend_type>
<backend_model>myCustomModule/config_identifier</backend_model>
...
</identifier>
Model/Config/Identifier.php
class Example_MyCustomModule_Model_Config_Identifier
extends Mage_Core_Model_Config_Data
{
protected function _afterSave()
{
if ($this->isValueChanged()) {
$path = $this->getValue();
// for each $store and $id combination...
Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$store.'/'.$id)
->setRequestPath($path.'/'.$id)
->save();
}
}
}