diff --git a/CHANGELOG.md b/CHANGELOG.md index 519e2f0fe7..1ed3850535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.23.1 - 2018-08-02 +## Added +- Add ie-compat meta tag to the index by default. [#316](https://github.com/plotly/dash/pull/316) +- Add `external_script` and `external_css` keywords to dash `__init__`. [#305](https://github.com/plotly/dash/pull/305) + ## 0.23.0 - 2018-08-01 ## Added - Dash components are now generated at build-time and then imported rather than generated when a module is imported. This should reduce the time it takes to import Dash component libraries, and makes Dash compatible with IDEs. diff --git a/dash/_utils.py b/dash/_utils.py index 1c6e5ca51c..bc71fde955 100644 --- a/dash/_utils.py +++ b/dash/_utils.py @@ -6,6 +6,20 @@ def interpolate_str(template, **data): return s +def format_tag(tag_name, attributes, inner='', closed=False, opened=False): + tag = '<{tag} {attributes}' + if closed: + tag += '/>' + elif opened: + tag += '>' + else: + tag += '>' + inner + '' + return tag.format( + tag=tag_name, + attributes=' '.join([ + '{}="{}"'.format(k, v) for k, v in attributes.items()])) + + class AttributeDict(dict): """ Dictionary subclass enabling attribute lookup/assignment of keys/values. diff --git a/dash/dash.py b/dash/dash.py index a561803057..7e64b7a973 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -23,6 +23,7 @@ from . import exceptions from ._utils import AttributeDict as _AttributeDict from ._utils import interpolate_str as _interpolate +from ._utils import format_tag as _format_tag _default_index = ''' @@ -61,7 +62,7 @@ # pylint: disable=too-many-instance-attributes -# pylint: disable=too-many-arguments +# pylint: disable=too-many-arguments, too-many-locals class Dash(object): def __init__( self, @@ -75,6 +76,8 @@ def __init__( compress=True, meta_tags=None, index_string=_default_index, + external_scripts=None, + external_stylesheets=None, **kwargs): # pylint-disable: too-many-instance-attributes @@ -128,6 +131,10 @@ def _handle_error(error): # static files from the packages self.css = Css() self.scripts = Scripts() + + self._external_scripts = external_scripts or [] + self._external_stylesheets = external_stylesheets or [] + self.registered_paths = {} # urls @@ -302,9 +309,12 @@ def _relative_url_path(relative_package_path='', namespace=''): def _generate_css_dist_html(self): links = self._collect_and_register_resources( self.css.get_all_css() - ) + ) + self._external_stylesheets + return '\n'.join([ - ''.format(link) + _format_tag('link', link, opened=True) + if isinstance(link, dict) + else ''.format(link) for link in links ]) @@ -325,9 +335,12 @@ def _generate_scripts_html(self): dash_renderer._js_dist ) ) + srcs = srcs[:-1] + self._external_scripts + [srcs[-1]] return '\n'.join([ - ''.format(src) + _format_tag('script', src) + if isinstance(src, dict) + else ''.format(src) for src in srcs ]) @@ -348,12 +361,11 @@ def _generate_meta_html(self): if not has_ie_compat: tags.append('') if not has_charset: - tags.append('') - for meta in self._meta_tags: - attributes = [] - for k, v in meta.items(): - attributes.append('{}="{}"'.format(k, v)) - tags.append(''.format(' '.join(attributes))) + tags.append('') + + tags = tags + [ + _format_tag('meta', x, opened=True) for x in self._meta_tags + ] return '\n '.join(tags) diff --git a/dash/version.py b/dash/version.py index 08a9dbff61..db714a8550 100644 --- a/dash/version.py +++ b/dash/version.py @@ -1 +1 @@ -__version__ = '0.23.0' +__version__ = '0.23.1' diff --git a/tests/test_integration.py b/tests/test_integration.py index b04675195b..2e8a2c75dc 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -434,4 +434,39 @@ def will_raise(): self.assertTrue('{%config%}' in exc_msg) self.assertTrue('{%scripts%}' in exc_msg) time.sleep(0.5) - print('invalid index string') + + def test_external_files_init(self): + js_files = [ + 'https://www.google-analytics.com/analytics.js', + {'src': 'https://cdn.polyfill.io/v2/polyfill.min.js'}, + { + 'src': 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.core.js', + 'integrity': 'sha256-Qqd/EfdABZUcAxjOkMi8eGEivtdTkh3b65xCZL4qAQA=', + 'crossorigin': 'anonymous' + } + ] + css_files = [ + 'https://codepen.io/chriddyp/pen/bWLwgP.css', + { + 'href': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css', + 'rel': 'stylesheet', + 'integrity': 'sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO', + 'crossorigin': 'anonymous' + } + ] + + app = dash.Dash( + external_scripts=js_files, external_stylesheets=css_files) + + app.layout = html.Div() + + self.startServer(app) + time.sleep(0.5) + + js_urls = [x['src'] if isinstance(x, dict) else x for x in js_files] + css_urls = [x['href'] if isinstance(x, dict) else x for x in css_files] + + for fmt, url in itertools.chain( + (("//script[@src='{}']", x) for x in js_urls), + (("//link[@href='{}']", x) for x in css_urls)): + self.driver.find_element_by_xpath(fmt.format(url))