Issue:
* There was no caching policy specified.
* -> Browsers use their own default policy.
* -> After upgrade, some Web UI files might have been actualized some not.
* -> With schema change may result into weird bugs in Web UI

Solution considerations:

1. Detect server version change and hard-reload at runtime
Detection is easy. Problem is the reload. Obvious candidate 'window.location.reload(true)' works in Firefox but not in Chrome because expected behavior when parameter is used is not in standard and therefore Chromium/WebKit authors did not implement it.

2. Application Cache
HTML 5 technology which lets web apps to run offline. Besides weird issues with event handlers which I encountered, this would be an ideal candidate. Simple change of manifest file would lead to reload of all files (requires reload of page to used the new files).

Showstopper was usage with untrusted certificate. If user did not add exception for the cert or its CA and would visit the page for a second time, all AJAX calls would fail.

3. Set Expires to now() for everything
Web UI rarely changes so this is an overkill. Setting it to different value is not a solution either. We can't predict when the upgrade will happen and when new Web UI will be needed.

Solution:
* Implemented a mini loader which loads basic resources. Dojo loader takes action after Dojo is loaded.
* The loader adds a version parameter (?v=__NUM_VERSION__) to all requests.
* Version is defined in the loader. It's set to current in `make version-update`.
* All static pages use this loader to fetch their resources.
* Version is also passed to dojo loader as cache-bust for the same effect.
* Expire header was set to 'access time plus 1 year' for /ui folder. Exceptions are HTML files and loader (set to immediate expiration).

Possible issues:
* Images are cached but not requested with version param.
  * Images with version and without are considered different
* -> We would have to attach version to all URIs - in CSS and in JS. But we should avoid changing jQuery UI CSS. * Proposed solution is to change image name when changing image. Image change is done rarely. * Version is set by build and therefore updated just on server update. It might cause trouble with different update schedule of plugins.
  * No action taken to address this issue yet.
  * We might leave it on plugin devs (own .conf in /etc/httpd/conf.d/)
  * or set expires to now for all plugins
* running `make version-update` is required in order to use static version of UI for testing

https://fedorahosted.org/freeipa/ticket/3798
--
Petr Vobornik
From b3c91019e66239f21e57c7e13784240c743cf918 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Thu, 29 Aug 2013 15:19:02 +0200
Subject: [PATCH] Load updated Web UI files after server upgrade

Issue:
* There was no caching policy specified.
* -> Browsers use their own default policy.
* -> After upgrade, some Web UI files might have been actualized some not.
* -> With schema change may result into weird bugs in Web UI

Solution considerations:

1. Detect server version change and hard-reload at runtime
Detection is easy. Problem is the reload. Obvious candidate 'window.location.reload(true)' works in Firefox but not in Chrome because expected behavior when parameter is used is not in standard and therefore Chromium/WebKit authors did not implement it.

2. Application Cache
HTML 5 technology which lets web apps to run offline. Besides weird issues with event handlers which I encountered, this would be an ideal candidate. Simple change of manifest file would lead to reload of all files (requires reload of page to used the new files).

Showstopper was usage with untrusted certificate. If user did not add exception for the cert or its CA and would visit the page for a second time, all AJAX calls would fail.

3. Set Expires to now() for everything
Web UI rarely changes so this is an overkill. Setting it to different value is not a solution either. We can't predict when the upgrade will happen and when new Web UI will be needed.

Solution:
* Implemented a mini loader which loads basic resources. Dojo loader takes action after Dojo is loaded.
* The loader adds a version parameter (?v=__NUM_VERSION__) to all requests.
* Version is defined in the loader. It's set to current in `make version-update`.
* All static pages use this loader to fetch their resources.
* Version is also passed to dojo loader as cache-bust for the same effect.
* Expire header was set to 'access time plus 1 year' for /ui folder. Exceptions are HTML files and loader (set to immediate expiration).

Possible issues:
* Images are cached but not requested with version param.
  * Images with version and without are considered different
  * -> We would have to attach version to all URIs - in CSS and in JS. But we should avoid changing jQuery UI CSS.
  * Proposed solution is to change image name when changing image. Image change is done rarely.
* Version is set by build and therefore updated just on server update. It might cause trouble with different update schedule of plugins.
  * No action taken to address this issue yet.
  * We might leave it on plugin devs (own .conf in /etc/httpd/conf.d/)
  * or set expires to now for all plugins
* running `make version-update` is required in order to use static version of UI for testing

https://fedorahosted.org/freeipa/ticket/3798
---
 .gitignore                       |   2 +-
 Makefile                         |   3 +-
 install/conf/ipa.conf            |   9 +++-
 install/html/browserconfig.html  |  29 ++++++----
 install/html/ssbrowser.html      |  49 ++++++++++-------
 install/html/unauthorized.html   |  16 +++---
 install/ui/Makefile.am           |   1 -
 install/ui/config.js             |  39 --------------
 install/ui/index.html            |  66 ++++++++++++++++-------
 install/ui/jsl.conf              |   2 +
 install/ui/login.html            |  15 ++++--
 install/ui/reset_password.html   |  15 ++++--
 install/ui/src/libs/Makefile.am  |   1 +
 install/ui/src/libs/loader.js.in | 113 +++++++++++++++++++++++++++++++++++++++
 14 files changed, 254 insertions(+), 106 deletions(-)
 delete mode 100644 install/ui/config.js
 create mode 100644 install/ui/src/libs/loader.js.in

diff --git a/.gitignore b/.gitignore
index 5252dad1befa9279b8e5cd033e395309f6a9ae8a..96c151a192e9ee890d1e9e45ae32a2bba06704c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,10 +54,10 @@ install/missing
 install/stamp-h1
 install/po/test.po
 install/po/test_locale/xh_ZA/LC_MESSAGES/ipa.mo
-install/ui/test/results
 install/ui/release
 install/ui/src/dojo
 install/ui/src/build
+install/ui/src/libs/loader.js
 install/ui/src/plugins
 ipa-client/COPYING
 ipa-client/ChangeLog
diff --git a/Makefile b/Makefile
index a21cf7e33275fd1a783e89baf237c8dcd8db6508..ecd3d9dbd1200b912fc2e95d2d47fea314871a89 100644
--- a/Makefile
+++ b/Makefile
@@ -116,13 +116,14 @@ version-update: release-update
 		freeipa.spec.in > freeipa.spec
 	sed -e s/__VERSION__/$(IPA_VERSION)/ version.m4.in \
 		> version.m4
-
 	sed -e s/__VERSION__/$(IPA_VERSION)/ ipapython/setup.py.in \
 		> ipapython/setup.py
 	sed -e s/__VERSION__/$(IPA_VERSION)/ ipapython/version.py.in \
 		> ipapython/version.py
 	sed -e s/__VERSION__/$(IPA_VERSION)/ ipatests/setup.py.in \
 		> ipatests/setup.py
+	sed -e s/__NUM_VERSION__/$(IPA_NUM_VERSION)/ install/ui/src/libs/loader.js.in \
+		> install/ui/src/libs/loader.js
 	perl -pi -e "s:__NUM_VERSION__:$(IPA_NUM_VERSION):" ipapython/version.py
 	perl -pi -e "s:__API_VERSION__:$(IPA_API_VERSION_MAJOR).$(IPA_API_VERSION_MINOR):" ipapython/version.py
 	touch -r ipapython/version.py.in ipapython/version.py
diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf
index 1a33f6287bc5bb4fa57700c51a429c771bec969e..f185f514662d58e39c0da6536b067a63869be6a6 100644
--- a/install/conf/ipa.conf
+++ b/install/conf/ipa.conf
@@ -1,5 +1,5 @@
 #
-# VERSION 13 - DO NOT REMOVE THIS LINE
+# VERSION 14 - DO NOT REMOVE THIS LINE
 #
 # This file may be overwritten on upgrades.
 #
@@ -114,6 +114,8 @@ Alias /ipa/config "/usr/share/ipa/html"
   AllowOverride None
   Satisfy Any
   Allow from all
+  ExpiresActive On
+  ExpiresDefault "access plus 0 seconds"
 </Directory>
 
 
@@ -135,6 +137,11 @@ Alias /ipa/ui "/usr/share/ipa/ui"
   AllowOverride None
   Satisfy Any
   Allow from all
+  ExpiresActive On
+  ExpiresDefault "access plus 1 year"
+  <FilesMatch "(index.html|loader.js|login.html|reset_password.html)">
+        ExpiresDefault "access plus 0 seconds"
+  </FilesMatch>
 </Directory>
 
 #  Simple wsgi scripts required by ui
diff --git a/install/html/browserconfig.html b/install/html/browserconfig.html
index a7784f75b8dabb19a5658b06a008bc3f4660823d..d867a19416756b2f4c11f279e49b9513c62c4cd2 100644
--- a/install/html/browserconfig.html
+++ b/install/html/browserconfig.html
@@ -3,16 +3,25 @@
 <head>
     <meta charset="utf-8">
     <title>IPA: Identity Policy Audit</title>
-
-    <link rel="stylesheet" type="text/css" href="../ui/jquery-ui.css" />
-    <link rel="stylesheet" type="text/css" href="../ui/ipa.css" />
-    <link rel="stylesheet" type="text/css" href="ipa_error.css" />
-
-    <script type="text/javascript" src="../ui/js/libs/jquery.js"></script>
-    <script type="text/javascript" src="../ui/js/libs/jquery-ui.js"></script>
-    <script type="text/javascript" src="krb.js"></script>
-    <script type="text/javascript" src="ffconfig.js"></script>
-    <script type="text/javascript"  src="ffconfig_page.js"></script>
+    <script type="text/javascript" src="../ui/js/libs/loader.js"></script>
+    <script type="text/javascript">
+        (function() {
+            var styles = [
+                '../ui/jquery-ui.css',
+                '../ui/ipa.css',
+                'ipa_error.css'
+            ];
+            var scripts = [
+                '../ui/js/libs/jquery.js',
+                '../ui/js/libs/jquery-ui.js',
+                'krb.js',
+                'ffconfig.js',
+                'ffconfig_page.js'
+            ];
+            ipa_loader.scripts(scripts);
+            ipa_loader.styles(styles);
+        })();
+    </script>
 </head>
 
 <body class="info-page">
diff --git a/install/html/ssbrowser.html b/install/html/ssbrowser.html
index 72fd573cf907e7ce3a27a17a2857633480cff9de..896cabcec4f8390ba4cb1c707d7bee8a8b353515 100644
--- a/install/html/ssbrowser.html
+++ b/install/html/ssbrowser.html
@@ -3,30 +3,41 @@
 <head>
 <meta charset="utf-8">
     <title>IPA: Identity Policy Audit</title>
-
-    <link rel="stylesheet" type="text/css" href="../ui/jquery-ui.css" />
-    <link rel="stylesheet" type="text/css" href="../ui/ipa.css" />
-    <link rel="stylesheet" type="text/css" href="ipa_error.css" />
-
-    <script type="text/javascript" src="../ui/js/libs/jquery.js"></script>
-    <script type="text/javascript" src="krb.js"></script>
+    <script type="text/javascript" src="../ui/js/libs/loader.js"></script>
     <script type="text/javascript">
-        $(document).ready(function() {
-            var domain = '.' + (IPA_DOMAIN || 'example.com');
-            $('.example-domain').text(domain);
+        (function() {
+            function loaded() {
+                $(document).ready(function() {
+                    var domain = '.' + (IPA_DOMAIN || 'example.com');
+                    $('.example-domain').text(domain);
 
-            if ($.browser.mozilla) {
-                var ff_config = $("#configurefirefox");
-                var obj = $('<object/>', {
-                    type: 'text/html',
-                    'class': 'browser-config'
+                    if ($.browser.mozilla) {
+                        var ff_config = $("#configurefirefox");
+                        var obj = $('<object/>', {
+                            type: 'text/html',
+                            'class': 'browser-config'
+                        });
+                        obj.prop('data', 'jar:/ipa/errors/configure.jar!/preferences.html');
+                        obj.appendTo(ff_config);
+                        ff_config.show();
+                    }
                 });
-                obj.prop('data', 'jar:/ipa/errors/configure.jar!/preferences.html');
-                obj.appendTo(ff_config);
-                ff_config.show();
             }
-        });
+
+            var styles = [
+                '../ui/jquery-ui.css',
+                '../ui/ipa.css',
+                'ipa_error.css'
+            ];
+            var scripts = [
+                '../ui/js/libs/jquery.js',
+                'krb.js'
+            ];
+            ipa_loader.scripts(scripts, loaded);
+            ipa_loader.styles(styles);
+        })();
     </script>
+
 </head>
 
 <body class="info-page">
diff --git a/install/html/unauthorized.html b/install/html/unauthorized.html
index 0fac88b98bc6eebeaa776af8341dfb5fdad4773d..5c5ed7e983ca433cb64186c03f56e63d2cb9588a 100644
--- a/install/html/unauthorized.html
+++ b/install/html/unauthorized.html
@@ -3,12 +3,16 @@
 <head>
     <meta charset="utf-8">
     <title>IPA: Identity Policy Audit</title>
-
-    <script type="text/javascript" src="../ui/js/libs/jquery.js"></script>
-
-    <link rel="stylesheet" type="text/css" href="../ui/jquery-ui.css" />
-    <link rel="stylesheet" type="text/css" href="../ui/ipa.css" />
-    <link rel="stylesheet" type="text/css" href="ipa_error.css" />
+    <script type="text/javascript" src="../ui/js/libs/loader.js"></script>
+    <script type="text/javascript">
+        (function() {
+            var styles = [
+                '../ui/ipa.css',
+                'ipa_error.css'
+            ];
+            ipa_loader.styles(styles);
+        })();
+    </script>
 </head>
 
 <body class="info-page">
diff --git a/install/ui/Makefile.am b/install/ui/Makefile.am
index 77aab17a656bd37f6767b76c11445541e8c06c7f..94bedace375c96ce501c1520028d23fff5409e61 100644
--- a/install/ui/Makefile.am
+++ b/install/ui/Makefile.am
@@ -10,7 +10,6 @@ SUBDIRS =  				\
 
 appdir = $(IPA_DATA_DIR)/ui
 app_DATA =				\
-	config.js			\
 	favicon.ico			\
 	index.html 			\
 	jquery-ui.css			\
diff --git a/install/ui/config.js b/install/ui/config.js
deleted file mode 100644
index e984676f3a68f3380f9ba987d763713101704c89..0000000000000000000000000000000000000000
--- a/install/ui/config.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/*  Authors:
- *    Petr Vobornik <pvobo...@redhat.com>
- *
- * Copyright (C) 2012 Red Hat
- * see file 'COPYING' for use and warranty information
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-var dojoConfig= {
-    baseUrl: "js",
-    has: {
-        'dojo-firebug': false,
-        'dojo-debug-messages': true
-    },
-    parseOnLoad: false,
-    async: true,
-    packages: [
-        {
-            name:'dojo',
-            location:'dojo'
-        },
-        {
-            name: 'freeipa',
-            location: 'freeipa'
-        }
-    ]
-};
\ No newline at end of file
diff --git a/install/ui/index.html b/install/ui/index.html
index 75ff829970a42c6efa0f62a61bf922d07fb779a5..4be834076e44f10d24cc1716ff137342ef0cce8a 100644
--- a/install/ui/index.html
+++ b/install/ui/index.html
@@ -1,32 +1,62 @@
 <!DOCTYPE html>
 <html>
 <head>
-<meta charset="utf-8">
+    <meta charset="utf-8">
     <title>IPA: Identity Policy Audit</title>
 
-
-    <link rel="stylesheet" type="text/css" href="jquery-ui.css" />
-    <link rel="stylesheet" type="text/css" href="ipa.css" />
-    <!--ie only stylesheet -->
     <!--[if IE]>
-    <link rel="stylesheet" type="text/css" href="ie.css" />
+    <meta id="ie-detector">
     <![endif]-->
-    <link rel="icon" type="image/ico" href="favicon.ico">
 
-    <script type="text/javascript" src="js/libs/json2.js"></script>
-    <script type="text/javascript" src="js/libs/jquery.js"></script>
-    <script type="text/javascript" src="js/libs/jquery-ui.js"></script>
-    <script type="text/javascript" src="js/libs/jquery.ordered-map.js"></script>
-    <script type="text/javascript" src="js/libs/browser.js"></script>
-
-
-    <script type="text/javascript" src="config.js"></script>
-    <script type="text/javascript" src="js/dojo/dojo.js"></script>
+    <script type="text/javascript" src="js/libs/loader.js"></script>
     <script type="text/javascript">
-        require(['freeipa/app'], function(app){ app.run(); });
+
+        var dojoConfig = {
+            baseUrl: "js",
+            has: {
+                'dojo-firebug': false,
+                'dojo-debug-messages': true
+            },
+            parseOnLoad: false,
+            async: true,
+            packages: [
+                {
+                    name:'dojo',
+                    location:'dojo'
+                },
+                {
+                    name: 'freeipa',
+                    location: 'freeipa'
+                }
+            ],
+            cacheBust: ipa_loader.num_version || ""
+        };
+
+        (function() {
+            var ie = !!document.getElementById('ie-detector');
+            var styles = ['jquery-ui.css', 'ipa.css'];
+            if (ie) styles.push('ie.css');
+            var icons = ['favicon.ico'];
+            var scripts = [
+                'js/libs/json2.js',
+                'js/libs/jquery.js',
+                'js/libs/jquery-ui.js',
+                'js/libs/jquery.ordered-map.js',
+                'js/libs/browser.js',
+                'js/dojo/dojo.js'
+            ];
+            ipa_loader.scripts(scripts, function() {
+                require(['freeipa/app'], function(app){ app.run(); });
+            });
+            ipa_loader.styles(styles);
+            ipa_loader.icons(icons);
+
+        })();
     </script>
 </head>
 
-<body></body>
+<body>
+    <noscript>This application requires JavaScript enabled.</noscript>
+</body>
 
 </html>
\ No newline at end of file
diff --git a/install/ui/jsl.conf b/install/ui/jsl.conf
index e9e3ecc5eb5dc92680d0f23012c6a68365fd131b..7c3be5864e8e28ed5ccec08d56bc5e43cdde5baf 100644
--- a/install/ui/jsl.conf
+++ b/install/ui/jsl.conf
@@ -120,6 +120,7 @@
 +define jQuery
 +define define
 +define require
++define ipa_loader
 
 ### Files
 # Specify which files to lint
@@ -128,6 +129,7 @@
 # or "+process Folder\Path\*.htm".
 #
 
++process index.html
 +process src/libs/browser.js
 +process src/libs/jquery.ordered-map.js
 +process src/freeipa/*.js
diff --git a/install/ui/login.html b/install/ui/login.html
index 5545e8834a38fd24a6f0debf263a56402be42dbc..f3a0dd37b3ca7bd180b425a786a78f5606e4be76 100644
--- a/install/ui/login.html
+++ b/install/ui/login.html
@@ -3,11 +3,16 @@
 <head>
     <meta charset="utf-8">
     <title>IPA: Identity Policy Audit</title>
-
-    <link rel="stylesheet" type="text/css" href="ipa.css" />
-
-    <script type="text/javascript" src="js/libs/jquery.js"></script>
-    <script type="text/javascript" src="login.js"></script>
+    <script type="text/javascript" src="js/libs/loader.js"></script>
+    <script type="text/javascript">
+        (function() {
+            ipa_loader.styles(['ipa.css']);
+            ipa_loader.scripts([
+                'js/libs/jquery.js',
+                'login.js'
+            ]);
+        })();
+    </script>
 </head>
 
 <body class="info-page login-page">
diff --git a/install/ui/reset_password.html b/install/ui/reset_password.html
index 4dbbb7aacd52fe4ab787a8db73ca780225a98307..560e8a5e45f13dc51af8bf458d0eaaa327e84d12 100644
--- a/install/ui/reset_password.html
+++ b/install/ui/reset_password.html
@@ -3,11 +3,16 @@
 <head>
     <meta charset="utf-8">
     <title>IPA: Identity Policy Audit</title>
-
-    <link rel="stylesheet" type="text/css" href="ipa.css" />
-
-    <script type="text/javascript" src="js/libs/jquery.js"></script>
-    <script type="text/javascript" src="reset_password.js"></script>
+    <script type="text/javascript" src="js/libs/loader.js"></script>
+    <script type="text/javascript">
+        (function() {
+            ipa_loader.styles(['ipa.css']);
+            ipa_loader.scripts([
+                'js/libs/jquery.js',
+                'reset_password.js'
+            ]);
+        })();
+    </script>
 </head>
 
 <body class="info-page login-page">
diff --git a/install/ui/src/libs/Makefile.am b/install/ui/src/libs/Makefile.am
index 807b9d8d3161b9d8c6767a748f5eedf73f804eb3..6991e474e712bebb53174dc13b68b386e4d37941 100644
--- a/install/ui/src/libs/Makefile.am
+++ b/install/ui/src/libs/Makefile.am
@@ -7,6 +7,7 @@ app_DATA =				\
 	jquery.ordered-map.js 		\
 	jquery-ui.js 			\
 	json2.js 			\
+	loader.js 			\
 	$(NULL)
 
 EXTRA_DIST =                            \
diff --git a/install/ui/src/libs/loader.js.in b/install/ui/src/libs/loader.js.in
new file mode 100644
index 0000000000000000000000000000000000000000..00cbda57b82e2a48945532df32f561780682d32e
--- /dev/null
+++ b/install/ui/src/libs/loader.js.in
@@ -0,0 +1,113 @@
+/*  Authors:
+ *    Petr Vobornik <pvobo...@redhat.com>
+ *
+ * Copyright (C) 2013 Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+(function() {
+
+    var loader = window.ipa_loader = {
+        num_version: '__NUM_VERSION__'
+    };
+
+    var head = document.getElementsByTagName('head')[0];
+
+    function link(path, rel) {
+        rel = rel || 'stylesheet';
+        var el = document.createElement('link');
+        el.setAttribute('rel', rel);
+        el.setAttribute('type', 'text/css');
+        el.setAttribute('href', path);
+        head.appendChild(el);
+    }
+
+    function js(path, callback) {
+        var el = document.createElement('script');
+        el.setAttribute('type', 'text/javascript');
+        el.setAttribute('src', path);
+        var done = false;
+        var onload = function () {
+            if (done) return;
+            done = true;
+            callback();
+        };
+
+        // older ie
+        el.onreadystatechange = function () {
+            if (el.readyState === 'loaded' || el.readyState === 'complete') {
+                el.onreadystatechange = null;
+                onload();
+            }
+        };
+        // others
+        el.onload = onload;
+
+        head.appendChild(el);
+    }
+
+    function synchronous_download(paths, callback) {
+
+        var dl = paths.splice(0, 1)[0];
+        if (dl) {
+            js(dl, function() {
+                synchronous_download(paths, callback);
+            });
+        } else {
+            if (callback) callback();
+        }
+    }
+
+    function updated(version) {
+        var storage = window.localStorage;
+        if (storage) {
+            var key = 'server-version';
+            var previous = storage.getItem(key);
+            storage.setItem(key, version);
+            return version && previous && version !== previous;
+        }
+    }
+
+    function version_suffix() {
+        var suffix = '';
+        if (loader.num_version) {
+            suffix = '?v=' + loader.num_version;
+        }
+        return suffix;
+    }
+
+    loader.scripts = function(scripts, callback) {
+        var suffix = version_suffix();
+        var syn_code = [];
+        for (var i=0; i < scripts.length; i++) {
+            syn_code.push(scripts[i]+suffix);
+        }
+        synchronous_download(syn_code, callback);
+    };
+
+    loader.styles = function(styles) {
+        var suffix = version_suffix();
+        for (var i=0; i < styles.length; i++) {
+            link(styles[i] + suffix);
+        }
+    };
+
+    loader.icons = function(icons) {
+        var suffix = version_suffix();
+        for (var i=0; i < icons.length; i++) {
+            link(icons[i] + suffix, 'icon');
+        }
+    };
+})();
\ No newline at end of file
-- 
1.8.3.1

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to