95 |
96 | In RapydML we can accomplish the same thing with less repeated code using a loop:
97 |
98 | div(id="content"):
99 | "I haven't yet put anything on this page"
100 | p:
101 | "Compatible with all major browsers:"
102 | for $browser in [Firefox, Chrome, IE, Opera, Safari]:
103 | img(src="$browser.jpg", alt="$browser")
104 |
105 | But what if our alt attributes don't happen to match our image names? That can be remedied as well, by grouping variables via sub-arrays:
106 |
107 | div(id="content"):
108 | "I haven't yet put anything on this page"
109 | p:
110 | "Compatible with all major browsers:"
111 | for $item in [[firefox, Firefox], [chrome, Chrome], [ie, Internet Explorer], [opera, Opera], [safari, Safari]]:
112 | img(src="$item[0].jpg", alt="$item[1]")
113 |
114 |
115 | Variables and Sequences
116 | -----------------------
117 | Notice how we declare variables in RapydML. All variables must be preceeded by $, like in perl. While awkward at first, this actually allows us to more easily distinguish variables from HTML tags, and allows us better syntax highlighting of variables, something Python doesn't have. The assignment operator is := instead of the typical = sign. This is to avoid clashing with HTML's = sign if it happens to be part of the variable's value, as shown in example below:
118 |
119 | $src := src="smiley.gif", alt="A Smiley"
120 | for $i in [0:100]:
121 | img($src)
122 |
123 | We don't need to quote whatever we're assigning to the variable, the leading/lagging whitespace will automatically get stripped, the whitespace in the middle will get preserved. Note the use of a shorthand to create an array of 101 consecutive integers. This shorthand works similar to Python's range() function. The first argument specifies the minimum, the second specifies the maximum (not maximum+1 like Python's range()), the third argument is optional and represents the step size. Here are some examples:
124 |
125 | [0:6] -> [0,1,2,3,4,5,6]
126 | [1:8:2] -> [1,3,5,7]
127 | [8:1:-1] -> [8,7,6,5,4,3,2,1]
128 | [8:1] -> []
129 |
130 |
131 | Methods and HTML Chunks
132 | -----------------------
133 | If you find yourself reusing the same HTML in multiple places, but not in sequential order (preventing you from using a loop), you can define a method, which you can then invoke. A RapydML method is a chunk of HTML, which then gets plugged in every time the method call occurs. For example, here is how we could create a navigation menu, reusing the same button logic:
134 |
135 | def navbutton($text):
136 | div(class='nav-button'):
137 | img(src="$text.png", alt="Navigate to $text")
138 |
139 | div(id='nav-menu'):
140 | navbutton('Main')
141 | navbutton('Products')
142 | navbutton('Blog')
143 | navbutton('About')
144 |
145 | There is another subtle, but extremely useful feature methods have. Method calls can have child elements. These children will get appended inside the last outer-level element declared inside the method. For example, if you're creating a page with multiple sections, and the content layout inside each section differs, you can do the following:
146 |
147 | def subsection($title):
148 | div(class="sub-section"):
149 | $title
150 | div(class="section-content")
151 |
152 | div(id="main"):
153 | subsection('Foo'):
154 | 'This is the foo section, it only has text'
155 | subsection('Bar'):
156 | img(src="image.png")
157 | div:
158 | 'Bar section has images, and sub-children'
159 |
160 | Both, Foo and Bar, will append the children inside of the `section-content` div. This is a very powerful feature, allowing method content not only to get dumped inside of your page, but also wrap around other content in your page - similar to Python decorators. There is even more to this feature, which you can read about in the `Advanced Usage` section.
161 |
162 |
163 | Math and String Manipulation
164 | ----------------------------
165 | Like many other abstraction languages, RapydML can do a lot of the common logic for you. It has no concept of unit conversion like SASS, but it can easily handle many other things, like string manipulation, arithmetic, and mixing colors.
166 |
167 |
168 | ### Arithmetic and Colors
169 |
170 | RapydML can handle basic arithmetic and color computations for you. For example, let's rewrite the above logic such that our navigation buttons start with dark blue and get brighter with each button:
171 |
172 | $color := #004
173 | def navbutton($text):
174 | div(class='nav-button', style="background: $color"):
175 | img(src="$text.png", alt="Navigate to $text")
176 |
177 | div(id='nav-menu'):
178 | for $i in ['Main', 'Products', 'Blog', 'About']:
179 | $color += #222
180 | navbutton($i)
181 |
182 | Note that `$color` needs to be updated outside of a function. This is because RapydML shadows function variables, like Python. Moving `$color += #222` inside the function will update a local copy of `$color`, which gets discarded after the function terminates. RapydML is smart about colors. You can use the regular `#RRGGBB` format, shorthand `#RGB` form, or even html-accepted color name like `"Brown"`. When using html colors, it's important to use double-quotes around the color, otherwise it will be treated as a regular string. The color name itself, however, is not case sensitive, you can use "DarkBlue" or "darkblue", for example, or even "dArKbLuE". If no color with that name exists the string will be treated as a regular string. Mathematical computations are not limited to colors. You can also use them to compute dimensions of HTML elements, perform computations that get output on the page, or even concatenate strings:
183 |
184 | $height := 100
185 | $padding := 2
186 | $total := $height + $padding + 1 # border width of 1
187 |
188 | $name := 'smiley'
189 | $imgurl := 'static/images/' + $name + '.png'
190 |
191 | You probably noticed the use of `+=`, which is a shorthand for incrementing. Here are a few other ones:
192 |
193 | $a += 2 # equivalent to: $a := $a + 2
194 | $a -= $b # equivalent to: $a := $a - $b
195 | $a *= $b*2 # equivalent to: $a := $a * ($b*2)
196 | $a /= 5 + 2 # equivalent to: $a := $a / (5 + 2)
197 |
198 | Lastly, there is a caveat about color arithmetic. While RapydML will limit min/max colors to be within allowed range, the color get converted to an integer when performing operations. This means that if you keep adding blue to a color that already reached maximum allowed amount of blue, RapydML will start adding green. This behavior could be modified in the future such that each channel is independent.
199 |
200 |
201 | ### Using Python Directly
202 |
203 | RapydML will handle simple arithmetic and string concatenation for you, but what if you require more complex logic? RapydML has access to the full firepower of python (or at least its stdlib, you can't import Python modules). For example, let's say we wrote the following function to generate a button linking to one of the social media websites:
204 |
205 | def socialMedia($name):
206 | div(class="social-media"):
207 | a(href="www.$name.com"):
208 | img(src="$name.png" alt="$name")
209 |
210 | The only problem is that the content of `$name` has to be in lowercase for the link to work. You could rename your PNG image to be in lowercase as well, but the `alt` text in all lowercase would look bad. Fortunately, RapydML can invoke Python's native logic if you use `python` prefix. Let's rewrite the above logic to capitalize the displayed text:
211 |
212 | def socialMedia($name):
213 | div(class="social-media"):
214 | a(href="www.$name.com"):
215 | img(src="$name.png" alt=python.str.capitalize("$name"))
216 |
217 | Likewise, we could have instead used `python.str.lower()` if the `$name` was already capitalized before being passed into the function. RapydML compiler also auto-imports `math` module, allowing you to use logic like `python.math.sin(1)`. The `python.` prefix is not needed for Python methods that are nested inside other Python methods, like the following example:
218 |
219 | python.math.sin(min(1, 2, 3, 4))
220 |
221 |
222 | Using Raw Text, HTML, or JavaScript
223 | -----------------------------------
224 | As you probably already noticed, none of the above features address the possibility of having non-XML-like data inside of your page. There are times, however, when you might want raw text on your page that doesn't get interpreted. For example, you might need to embed JavaScript or CSS directly inside of your HTML, or might want to insert a raw chunk of HTML inside a tag when you can't get the markup just right via RapydML.
225 |
226 |
227 | ### Verbatim Method
228 |
229 | There were multiple alternatives I looked into for this, in the end I settled on one that seemed cleanest. RapydML introduces a verbatim() method, which if specified for the tag, will treat everything indented under the tag as raw text, putting it inside the HTML page as is. For example, let's add a Javascript tag. We can do it a couple different ways:
230 |
231 | javascript = verbatim('')
232 |
233 | The second way, since we already have a 'script' tag defined for us by default that works exactly like we need to, is to pass in an existing tag to use as a template:
234 |
235 | javascript = verbatim(script(type="text/javascript"))
236 |
237 | Now, should we choose to add raw JavaScript to our code, we can write it as follows:
238 |
239 | body:
240 | h1:
241 | 'Page Title'
242 | javascript:
243 | function factorial(n) {
244 | if (n === 0) {
245 | return 1;
246 | }
247 | return n * factorial(n - 1);
248 | }
249 | div:
250 | label:
251 | 'Text goes here'
252 |
253 | As soon as indentation resets to same level or higher as the verbatim tag, the raw JavaScript stops, and we start interpreting tags again. There is also verbatim_line() method available, that works the same way, but condenses the code into a single line by replacing `\n` with spaces and removing indentation. This is useful when you want to compress the chunk of raw code into a single line.
254 |
255 |
256 | ### Variables Inside of Verbatim Blocks
257 |
258 | It would be nice if the logic inside verbatim blocks would never need access to outside information. In real life, however, that's not always the case. Imagine that we need to include a chunk of JavaScript for creating a JSON call inside of our page. The format of this JSON call is identical across all of our pages on the website, with the only difference being the name of the page requested. The RapydML for this might look something like this:
259 |
260 | javascript:
261 | $(document).ready(function() {
262 | $.getJSON('call/json/get_docs/True?owner=pyjeon&repo=$page', function(html_docs) {
263 | $("div#documentation").append(html_docs);
264 | });
265 | }
266 |
267 | The problem here, however, is that since `javascript` was defined as a verbatim tag, the variable `$page` will not get replaced. One work-around is to add this JavaScript to each page, modifying the value of `$page` individually for each use. Surely, there must be a better way. There is, RapydML allows you to specify which variables are to be replaced on per-tag basis. You can specify them as arguments for the verbatim tag:
268 |
269 | javascript($page):
270 | $(document).ready(function() {
271 | $.getJSON('call/json/get_docs/True?owner=pyjeon&repo=$page', function(html_docs) {
272 | $("div#documentation").append(html_docs);
273 | });
274 | }
275 |
276 | Now every occurence of `$page` within the verbatim tag will be replaced. Note that the variables only apply to the current instance of the tag, and that only the specified values will get replaced:
277 |
278 | $foo := "one"
279 | $bar := "two"
280 |
281 | javascript:
282 | var a = $foo + $bar; // var a = $foo + $bar;
283 |
284 | javascript($foo):
285 | var a = $foo + $bar; // var a = "one" + $bar;
286 |
287 | javascript($bar):
288 | var a = $foo + $bar; // var a = $foo + "two";
289 |
290 | javascript($foo, $bar):
291 | var a = $foo + $bar; // var a = "one" + "two";
292 |
293 |
294 | ### Using Other Languages
295 |
296 | Verbatim tag is convenient when you want to include raw JavaScript inside of your page. But what if you're using RapydScript, CoffeeScript, or something else for your JavaScript? The typical way to include those is by having the `