Bascially I'm writing a templating system for my CMS and I want to have a modular structure which involves people putting in tags like:
<module name="news" /> or <include name="anotherTemplateFile" /> which I then want to find in my php and replace with dynamic html.
Someone on here pointed me towards DOMDocument, but I've already come across a problem.
I'm trying to find all <include /> tags in my template and replace them with some simple html. Here is my template code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>CMS</title>
<include name="head" />
</head>
<body>
<include name="header" />
<include name="content" />
<include name="footer" />
</body>
</html>
And here is my PHP:
$template = new DOMDocument();
$template->load("template/template.tpl");
foreach( $template->getElementsByTagName("include") as $include ) {
$element = '<input type="text" value="'.print_r($include, true).'" />';
$output = $template->createTextNode($element);
$template->replaceChild($output, $include);
}
echo $template->saveHTML();
Now, I get the fatal error Uncaught exception 'DOMException' with message 'Not Found Error'.
I've looked this up and it seems to be that because my <include /> tags aren't necessarily DIRECT children of $template its not replacing them.
How can I replace them independently of descent?
Thank you
Tom
EDIT
Basically I had a brainwave of sorts. If I do something like this for my PHP I see its trying to do what I want it to do:
$template = new DOMDocument();
$template->load("template/template.tpl");
foreach( $template->getElementsByTagName("include") as $include ) {
$element = '<input type="text" value="'.print_r($include, true).'" />';
$output = $template->createTextNode($element);
// this line is different:
$include->parentNode->replaceChild($output, $include);
}
echo $template->saveHTML();
However it only seems to change 1 occurence in the <body> of my HTML... when I have 3. :/
This is a problem with your DOMDocument->load, try
$template->loadHTMLFile("template/template.tpl");
But you may need to give it a .html extension.
this is looking for a html or an xml file. also, whenever you are using DOMDocument with html it is a good idea to use libxml_use_internal_errors(true); before the load call.
OKAY THIS WORKS:
foreach( $template->getElementsByTagName("include") as $include ) {
if ($include->hasAttributes()) {
$includes[] = $include;
}
//var_dump($includes);
}
foreach ($includes as $include) {
$include_name = $include->getAttribute("name");
$input = $template->createElement('input');
$type = $template->createAttribute('type');
$typeval = $template->createTextNode('text');
$type->appendChild($typeval);
$input->appendChild($type);
$name = $template->createAttribute('name');
$nameval = $template->createTextNode('the_name');
$name->appendChild($nameval);
$input->appendChild($name);
$value = $template->createAttribute('value');
$valueval = $template->createTextNode($include_name);
$value->appendChild($valueval);
$input->appendChild($value);
if ($include->getAttribute("name") == "head") {
$template->getElementsByTagName('head')->item(0)->replaceChild($input,$include);
}
else {
$template->getElementsByTagName("body")->item(0)->replaceChild($input,$include);
}
}
//$template->load($nht);
echo $template->saveHTML();
However it only seems to change 1 occurence in the of my HTML... when I have 3. :/
DOM NodeLists are ‘live’: when you remove an <include> element from the document (by replacing it), it disappears from the list. Conversely if you add a new <include> into the document, it will appear in your list.
You might expect this for a NodeList that comes from an element's childNodes, but the same is true of NodeLists that are returned getElementsByTagName. It's part of the W3C DOM standard and occurs in web browsers' DOMs as well as PHP's DOMDocument.
So what you have here is a destructive iteration. Remove the first <include> (item 0 in the list) and the second <include>, previously item 1, become the new item 0. Now when you move on to the next item in the list, item 1 is what used to be item 2, causing you to only look at half the items.
PHP's foreach loop looks like it might protect you from that, but actually under the covers it's doing exactly the same as a traditional indexed for loop.
I'd try to avoid creating a new templating language for PHP; there are already so many, not to mention PHP itself. Creating one out of DOMDocument is also going to be especially slow.
eta: In general regex replace would be faster, assuming a simple match pattern that doesn't introduce loads of backtracking. However if you are wedded to an XML syntax, regex isn't very good at parsing that. But what are you attempting to do, that can't already be done with PHP?
<?php function write_header() { ?>
<p>This is the header bit!</p>
<? } ?>
<body>
...
<?php write_header(); ?>
...
</body>
Related
Lets say I have a gsp page that I want to load all the scripts in a particular folder:
<html>
<head>
{each file in $path}
<asset:javascript src="file" />
</head>
</html>
assuming $path is a directory path. Most templating languages have a way to do this so I'm sure Grails can accomplish it. But I'm not sure how to make it happen. My end goal is this:
ndex
<html>
<head>
<asset:javascript src="js/util.js" />
<asset:javascript src="js/util2.js" />
<asset:javascript src="js/index.js" />
</head>
</html>
Please help.
You can do something like this:
<html>
<head>
<g:each var="file" in="${(new File(path)).listFiles()*.path}">
<asset:javascript src="${file}" />
</g:each>
</head>
</html>
The GSP g:each tag is how iteration is performed in Grails when using GSP. The in attribute is used to specify the Iterable to iterate through. In this case it's the expression:
(new File(path)).listFiles()*.path
The expression means:
new File(path) - Create a Java File object to represent the directory path.
.listFiles() - Returns a list of all files and directories (excluding sub-directories) each represented by File objects.
*.path - A spread operator expression which returns a list of file path Strings, effectively converting the File objects into Strings. It's the equivalent of .collect { it.path }.
I´m trying to render a dynamic database title in asp.net mvc view. So I have something like this in my view.
#section meta {
<meta name="title" content="#Model.title" />
}
When model has special characters like Misión in spanish it shows in title something like
Misión ... I´m using meta charset utf8 in my layout. Is there a special encoding I´m missing ?
How can I render Misión in title page ?
Using #someproperty will assume you're rendering out HTML and make sure it gets encoded to prevent things like cross site scripting. In this instance you want it to render the raw value, in which case you need to use Html.Raw(...) to render your content in it's raw form.
#section meta {
<meta name="title" content="#Html.Raw(Model.title)" />
}
However, just be aware that if the Model.title can come from user generated content (or some other untrusted source), you could be opening yourself up to security issues (for example if your Model.title's value was "test" /> <script ...etc...", a malicious user could use it to inject code into your pages.
Edit: Just including the content of my comment below for future googlers, since it appears that was the actual solution...
If you put the #Html.Raw(Model.title) directly in the page somewhere (i.e. not in the meta tag) and it works correctly there, you may be facing the same problem discussed here, in which case you could work around it by using the slightly uglier:
#section meta {
<meta name="title" #Html.Raw("content=\" + Model.title + "\"") />
}
Approach - 1
string value1 = "<html>"; // <html>
string value2 = HttpUtility.HtmlDecode(value1); // <html> //While getting
string value3 = HttpUtility.HtmlEncode(value2); // <html> //While saving
Approach - 2
Html.Raw("PKKG StackOverFlow"); // PKKG StackOverFlow
I decided to use jQuery UI for my autocomplete opposed to a plugin because I read that the plugins are deprecated. My overall goal is to have an autocomplete search bar that hits my database and returns users suggestions of city/state or zipcodes in a fashion similar to google. As of now I am not even sure that the .autocomplete function is being called. I scratched everything I had and decided to start with the basics. I downloaded the most recent version of jQuery UI from http://jqueryui.com/download and am trying to get the example that they use here http://jqueryui.com/demos/autocomplete/ to work. All the scripts that I have included seem to be connected at least linked through Dreamworks so I am fairly certain that the paths I have included are correct. The CSS and Javascripts that I have included are unaltered straight from the download. Below is my HTML code and my backend PHP code that is returning JSon formated data. Please help me. Maybe I need to include a function that deals with the JSon returned data but I am trying to follow the example although I see that they used a local array.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>jQueryUI Demo</title>
<link rel="stylesheet" href="css/ui-lightness/jquery-ui-1.8.17.custom.css" type="text/css" />
<script type="text/javascript" src ="js/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src ="js/jquery-ui-1.8.17.custom.min.js"></script>
</script>
<script type="text/javascript">
$(document).ready(function() {
$("#tags").autocomplete({
source: "search_me.php"
});
});
</script>
</head>
<body>
<div class="demo">
<div class="ui-widget">
<label for="tags">Tags: </label>
<input id="tags" />
</div>
</div><!-- End demo -->
<div class="demo-description">
<p>The Autocomplete widgets provides suggestions while you type into the field. Here the suggestions are tags for programming languages, give "ja" (for Java or JavaScript) a try.</p>
<p>The datasource is a simple JavaScript array, provided to the widget using the source-option.</p>
</div><!-- End demo-description -->
</body>
</html>
Below the PHP part.
<?php
include 'fh.inc.db.php';
$db = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD) or
die ('Unable to connect. Check your connection parameters.');
mysql_select_db(MYSQL_DB, $db) or die(mysql_error($db));
$location = htmlspecialchars(trim($_GET['term'])); //gets the location of the search
$return_arr = array();
if(is_numeric($location)) {
$query = "SELECT
zipcode_id
FROM
user_zipcode
WHERE
zipcode_id REGEXP '^$location'
ORDER BY zipcode_id DESC LIMIT 10";
$result = mysql_query($query, $db) or die(mysql_error($db));
while($row = mysql_fetch_assoc($result)) {
extract($row);
$row_array['zipcode_id'] = $zipcode_id;
array_push($return_arr, $row_array);
}
}
mysql_close($db);
echo json_encode($return_arr);
?>
Thanks for the ideas. Here is an update.
I checked the xhr using firebug and made sure that it is responding thanks for that tip. also the above php code I hadn't initialized $return_arr so i took care of that. Also thanks for the clarification of the js required or rather not required. Now when I type in a zipcode a little box about a centimeter shows up underneath it but I can't see if anything is in there, I would guess not. I went to my php page and set it up to manually set the variable to "9408" and loaded the php page directly through my browser to see what it returned. This is what it returned.
[{"zipcode_id":"94089"},{"zipcode_id":"94088"},{"zipcode_id":"94087"},{"zipcode_id":"94086"},{"zipcode_id":"94085"},{"zipcode_id":"94083"},{"zipcode_id":"94080"}]
I then went to a JSON code validator at this url http://jsonformatter.curiousconcept.com/ at it informed me that my code is in fact returning JSON formatted data. Anymore suggestions to help me troubleshoot the problem would be terrific.
Wow after more research I stumbled across the answer on someone another post.
jquery autocomplete not working with JSON data
Pretty much the JSON returned data must contain Label or Value or both. Switched the zipcode_id to value in my $row_array and... boom goes the dynamite!
Your scripts (js files) references are not correct, should only be:
<!-- the jquery library -->
<script type="text/javascript" src ="js/jquery-1.7.1.min.js"></script>
<!-- the full compressed and minified jquery UI library -->
<script type="text/javascript" src ="js/jquery-ui-1.8.17.custom.min.js"></script>
The files "jquery.ui.core.js", "jquery.ui.widget.js" and "jquery.ui.position.js" are the separated development files, the jquery ui library is splitted into modules.
The file "jquery-ui-1.8.17.custom.min.js" contains them all, compressed and minified !
Concerning the data source, as stated in the "Overview" section of the Autocomplete documentation: when using a an URL, it must return json data, either of the form of:
an simple array of strings: ['string1', 'string2', ...]
or an array of objects with label (and a value - optionnal) property [{ label: "My Value 1", Value: "AA" }, ...]
I'm really not familiar with PHP so just make sure your php script returns one of those :-)
I have a template and in its Definition I use several forms and buttons.
The problem is the definition (define) xhtml file does not know the component hierarchy.
And for example I want to update the element "table2" in a different form in the same define file.
Template Insert:
<p:tabView id="nav"> <!-- nav -->
<ui:insert name="content_nav">content navigation</ui:insert>
</p:tabView>
defines the first level of my hierarchy "nav"
Template define:
<ui:define name="content_nav">
<h:form id="form1"> <!-- nav:form1 -->
<h:dataTable id="table1"/> <!-- nav:form1:table1 -->
<p:inputText value="#{bean.value}"/>
<p:commandButton action="..." update="nav:form2:table2"/>
</h:form>
<h:form id="form2">
<h:dataTable id="table2"/> <!-- nav:form2:table2 -->
<!-- other elements -->
</h:form>
</ui:define>
In my define part I don't want to know "nav"!
How can I do this? or how can I move one naming component upwards?, or save the highest parent complete id in a variable?
sometimes i saw something like:
update=":table2"
But I could not find any informations about this?, the JavaEE 6 documentation just mentions the # keywords.
Ugly, but this should work out for you:
<p:commandButton action="..." update=":#{component.namingContainer.parent.namingContainer.clientId}:form2:table2" />
As you're already using PrimeFaces, an alternative is to use #{p:component(componentId)}, this helper function scans the entire view root for a component with the given ID and then returns its client ID:
<p:commandButton action="..." update=":#{p:component('table2')}" />
ugly answer works well
update=":#{component.namingContainer.parent.namingContainer.clientId}:form2:table2
mainly more useful updating from opened dialog to parent datatable
You may use binding attribute to declare EL variable bound to JSF component. Then you may access absolute client id of this component by using javax.faces.component.UIComponent.getClientId(). See example below:
<t:selectOneRadio
id="yourId"
layout="spread"
value="#{yourBean.value}"
binding="#{yourIdComponent}">
<f:selectItems value="#{someBean.values}" />
</t:selectOneRadio>
<h:outputText>
<t:radio for=":#{yourIdComponent.clientId}" index="0" />
</h:outputText>
Try this:
<h:commandButton value="Click me">
<f:ajax event="click" render="table" />
</h:commandButton>
Additionally to the solutions above I had the problem, that I had to dynamically generate the to-be-updated components (many) based on server-side logic (with maybe harder to find out nesting).
So the solution on the server-side is an equivalent to update=":#{p:component('table2')}"1 which uses org.primefaces.util.ComponentUtils.findComponentClientId( String designId ):
// UiPnlSubId is an enum containing all the ids used within the webapp xhtml.
// It could easily be substituted by a string list or similar.
public static String getCompListSpaced( List< UiPnlSubId > compIds ) {
if ( compIds == null || compIds.isEmpty() )
return "" ;
StringBuffer sb = new StringBuffer( ":" ) ;
for ( UiPnlSubId cid : compIds )
sb.append( ComponentUtils.findComponentClientId( cid.name() ) ).append( " " ) ;
return sb.deleteCharAt( sb.length() - 1 ).toString() ; // delete suffixed space
}
called via some other method using it, e.g. like ... update="#{foo.getCompListComputed( 'triggeringCompId' )}".
1: first I tried without too much thinking to return public static String getCompListSpaced0() { return ":#{p:component('table2')}" ; } in an ... update="#{foo.getCompListSpaced0()} expression, which of course (after thinking about how the framework works :) ) is not resolved (returned as is) and may cause the issues with it some users experienced. Also my Eclipse / JBoss Tools environment suggested to write :#{p.component('table2')} ("." instead of ":") which did not help - of course.
I have 2 tables, page and settings.
page is just a bunch of fields, such as name and slug, and has 3 other fields for meta tags (title, keywords, description) and displays a cms page.
The settings has 3 fields: default_meta_title, default_meta_keywords, default_meta_description
Now what I'm looking to do is to display the default_meta_* tags in the HTML source if the page I am on does not have the particular meta info set from the cms page.
All pages, except the homepage is managed this way, so I was thinking I'd need to add some code to the layout.php to get this to work.
So the homepage will display my default_meta_*, as I cannot set this in the cms pages table.
There are two ways to solve the problem.
First is to use sfYaml class to update view.yml with default meta tags (see documentation about view.yml). After that if specific page should use another metas you can override defaults with addMeta method of response object
Second (as ManseUK suggested) is to declare slot placing code like this into layout
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<?php include_javascripts() ?>
<?php include_stylesheets() ?>
<?php include_title() ?>
<?php if (has_slot('metas')): ?>
<?php include_slot('metas') ?>
<?php else: ?>
<?php include_component('page', 'metas') ?>
<?php endif; ?>
<link rel="shortcut icon" href="/favicon.ico" />
</head>
<body>
Default metas will be rendered via page components. On top of your template (i guess modules/page/templates/showSuccess.php) place code
<?php slot('metas') ?>
<?php if($page->hasMetas()):?>
<!-- code to render nondefault page metas -->
<?php echo $page->getMetas(); ?>
<?php else: ?>
<?php include_component('page', 'metas') ?>
<?php endif;?>
<?php end_slot() ?>
I assume that you will replace $page->hasMetas() with real code that will check if your page object has metatags.
Actually i would prefer to go further and code page components to accept parameters. Code in a template will look like
<?php slot('metas') ?>
<?php include_component('page', 'metas', array('metas'=>$page->getMetas())) ?>
<?php end_slot() ?>
Deciding which metas (default or not) should be rendered will take place in page components (i assume that you can easily retrieve defaul;t settings from your database). If no parameters were passed (see layout code) than your component should also render default metas.
I hope this will help.
You could use a slot - check for existence of the slot in the layout - if it exists then add the custom meta fields - if not add the default ones