├── .bowerrc
├── .editorconfig
├── .ember-cli
├── .gitignore
├── .jshintrc
├── .npmignore
├── .travis.yml
├── .watchmanconfig
├── LICENSE.md
├── README.md
├── addon
├── components
│ ├── lazy-background-image.js
│ └── lazy-image.js
├── lib
│ └── cache.js
└── mixins
│ ├── image-load.js
│ └── lazy-image.js
├── app
├── components
│ ├── lazy-background-image.js
│ └── lazy-image.js
└── templates
│ └── components
│ ├── lazy-background-image.hbs
│ └── lazy-image.hbs
├── bower.json
├── config
├── ember-try.js
└── environment.js
├── ember-cli-build.js
├── index.js
├── package.json
├── testem.json
├── tests
├── .jshintrc
├── dummy
│ ├── app
│ │ ├── app.js
│ │ ├── components
│ │ │ └── .gitkeep
│ │ ├── controllers
│ │ │ └── .gitkeep
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── index.html
│ │ ├── models
│ │ │ └── .gitkeep
│ │ ├── router.js
│ │ ├── routes
│ │ │ ├── .gitkeep
│ │ │ └── index.js
│ │ ├── styles
│ │ │ ├── .gitkeep
│ │ │ └── app.css
│ │ ├── templates
│ │ │ ├── application.hbs
│ │ │ ├── components
│ │ │ │ └── .gitkeep
│ │ │ └── index.hbs
│ │ └── views
│ │ │ └── .gitkeep
│ ├── config
│ │ └── environment.js
│ └── public
│ │ ├── crossdomain.xml
│ │ └── robots.txt
├── helpers
│ ├── resolver.js
│ └── start-app.js
├── index.html
├── test-helper.js
└── unit
│ └── components
│ └── lazy-image-test.js
└── vendor
└── lazy-image
└── lazy-image.css
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "analytics": false
4 | }
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.js]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [*.hbs]
21 | insert_final_newline = false
22 | indent_style = space
23 | indent_size = 2
24 |
25 | [*.css]
26 | indent_style = space
27 | indent_size = 2
28 |
29 | [*.html]
30 | indent_style = space
31 | indent_size = 2
32 |
33 | [*.{diff,md}]
34 | trim_trailing_whitespace = false
35 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": false
9 | }
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # misc
12 | /.sass-cache
13 | /connect.lock
14 | /coverage/*
15 | /libpeerconnection.log
16 | npm-debug.log
17 | testem.log
18 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "-Promise"
6 | ],
7 | "browser": true,
8 | "boss": true,
9 | "curly": true,
10 | "debug": false,
11 | "devel": true,
12 | "eqeqeq": true,
13 | "evil": true,
14 | "forin": false,
15 | "immed": false,
16 | "laxbreak": false,
17 | "newcap": true,
18 | "noarg": true,
19 | "noempty": false,
20 | "nonew": false,
21 | "nomen": false,
22 | "onevar": false,
23 | "plusplus": false,
24 | "regexp": false,
25 | "undef": true,
26 | "sub": true,
27 | "strict": false,
28 | "white": false,
29 | "eqnull": true,
30 | "esnext": true,
31 | "unused": true
32 | }
33 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | bower_components/
2 | tests/
3 | tmp/
4 | dist/
5 |
6 | .bowerrc
7 | .editorconfig
8 | .ember-cli
9 | .travis.yml
10 | .npmignore
11 | **/.gitkeep
12 | bower.json
13 | ember-cli-build.js
14 | Brocfile.js
15 | testem.json
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | - "0.12"
5 |
6 | sudo: false
7 |
8 | cache:
9 | directories:
10 | - node_modules
11 |
12 | env:
13 | matrix:
14 | - EMBER_TRY_SCENARIO=ember-release
15 | - EMBER_TRY_SCENARIO=ember-beta
16 | - EMBER_TRY_SCENARIO=ember-canary
17 |
18 | matrix:
19 | fast_finish: true
20 | allow_failures:
21 | - env: EMBER_TRY_SCENARIO=ember-beta
22 | - env: EMBER_TRY_SCENARIO=ember-canary
23 |
24 | before_install:
25 | - "npm config set spin false"
26 | - "npm install -g npm@^2"
27 |
28 | install:
29 | - npm install -g bower
30 | - npm install
31 | - bower install
32 |
33 | script:
34 | - ember try $EMBER_TRY_SCENARIO test
35 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp"]
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ember-lazy-image
2 |
3 | [](https://travis-ci.org/twokul/ember-lazy-image)
4 |
5 | `ember-lazy-image` is a component that allows you to gracefully handle image loading.
6 |
7 | Component will load images lazily, only if they appeared in the view port.
8 | This optimization brings page load time down.
9 |
10 | Default loading placeholder is stolen from [aurer](https://github.com/aurer) and his awesome [codepen](http://codepen.io/aurer/pen/jEGbA).
11 |
12 | ### Installation
13 |
14 | From inside your ember-cli project, run the following:
15 |
16 | ```bash
17 | ember install ember-lazy-image
18 | ```
19 |
20 | #### Installation for ember-cli 0.1.5 - 0.2.2
21 |
22 | ```bash
23 | ember install:addon ember-lazy-image
24 | ```
25 |
26 | #### Installation without ember-cli
27 |
28 | ```bash
29 | npm install ember-lazy-image --save
30 | ```
31 |
32 | ### lazy-image
33 |
34 | ```hbs
35 | {{lazy-image url='http://my-valid-url.com/foo.jpg'}}
36 | ```
37 |
38 | Component will wait until the image is loaded and while waiting it will show default
39 | loading placeholder (see above).
40 |
41 | You can customize `loading` placeholder by passing it as an parameter:
42 |
43 | ```hbs
44 | {{#lazy-image url='http://my-valid-url.com/foo.jpg'}}
45 |
46 | {{/lazy-image}}
47 | ```
48 |
49 | You can also define the fallback if the image failed to load. By default, component will render
50 | `Image failed to load` text.
51 |
52 | You can customize `error` text by passing it as an parameter:
53 |
54 | ```hbs
55 | {{lazy-image url='http://my-not-valid-url.com/foo.jpg' errorText='Something went wrong.'}}
56 | ```
57 |
58 | ### `width`, `height` and `data-*` attributes
59 |
60 | Lazy Image supports `width`, `height` and `data-*` attribute bindings.
61 |
62 | ```hbs
63 | {{lazy-image url='http://my-valid-url.com/foo.jpg' width=400 height=400 data-foo-bar="my-foo-bar"}}
64 | {{lazy-image url='http://my-valid-url.com/foo.jpg' width=400 height=400 data-foo-bar=foo.bar.path}}
65 | ```
66 |
67 | ### `class` attribute
68 |
69 | You can also pass class names for the image element.
70 |
71 | ```hbs
72 | {{lazy-image url='http://my-valid-url.com/foo.jpg' class='foo-bar baz-bar'}}
73 | ```
74 |
75 | ### `alt` attribute
76 |
77 | You can pass the alt attribute to the component and it will be rendered on the image element
78 |
79 | ```hbs
80 | {{lazy-image url='http://my-valid-url.com/foo.jpg' alt='foo description'}}
81 | ```
82 |
83 | ### ember-in-viewport options
84 |
85 | Lazy Image uses [ember-in-viewport](https://github.com/dockyard/ember-in-viewport/) for viewport detection. Due to the way listeners and `requestAnimationFrame` is setup, you'll have to override the `ember-in-viewport` options by creating `app/components/lazy-image.js`:
86 |
87 | ```js
88 | // app/components/lazy-image.js
89 |
90 | import Ember from 'ember';
91 | import LazyImage from 'ember-lazy-image/components/lazy-image';
92 |
93 | export default LazyImage.extend({
94 | viewportOptionsOverride: Ember.on('didInsertElement', function() {
95 | Ember.setProperties(this, {
96 | viewportUseRAF : true,
97 | viewportSpy : false,
98 | viewportRefreshRate : 150,
99 | viewportTolerance: {
100 | top : 50,
101 | bottom : 50,
102 | left : 20,
103 | right : 20
104 | }
105 | });
106 | })
107 | });
108 | ```
109 |
110 | See [Advanced usage (options)](https://github.com/dockyard/ember-in-viewport/tree/1.0.0#advanced-usage-options) for more in detail `ember-in-viewport` options.
111 |
112 | The use of `threshold` is deprecated in favor of `viewportTolerance`.
113 |
114 | ### Experimental `lazy-background-image`
115 |
116 | This is an experimental component that will add `background-image` style attribute to a component's `div`. It also
117 | sets `min-height` attribute to `270px` so the image is visible. You should be able to overwrite it by using `lazy-background-image` class.
118 |
119 | ## Installation
120 |
121 | * `git clone` this repository
122 | * `npm install`
123 | * `bower install`
124 |
125 | ## Running
126 |
127 | * `ember server`
128 | * Visit your app at http://localhost:4200.
129 |
130 | ## Running Tests
131 |
132 | * `ember test`
133 | * `ember test --server`
134 |
135 | ## Building
136 |
137 | * `ember build`
138 |
139 | For more information on using ember-cli, visit [http://www.ember-cli.com/](http://www.ember-cli.com/).
140 |
--------------------------------------------------------------------------------
/addon/components/lazy-background-image.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import ImageLoadMixin from '../mixins/image-load';
3 | import LazyImageMixin from '../mixins/lazy-image';
4 | import InViewportMixin from 'ember-in-viewport';
5 |
6 | const { htmlSafe } = Ember.String;
7 | const { computed, Component } = Ember;
8 |
9 | export default Component.extend(InViewportMixin, ImageLoadMixin, LazyImageMixin, {
10 | attributeBindings: ['width', 'height', 'style'],
11 |
12 | style: computed('lazyUrl', 'opacity', function() {
13 | const opacity = this.get('opacity');
14 | const style = opacity ? `background-image: linear-gradient(rgba(0, 0, 0, ${opacity}), rgba(0, 0, 0, ${opacity})), url(${this.get('lazyUrl')})` : `background-image: url(${this.get('lazyUrl')})`;
15 |
16 | return htmlSafe(style);
17 | }),
18 |
19 | classNames: ['lazy-background-image'],
20 |
21 | _setupAttributes() {
22 | const component = this;
23 | const keys = Object.keys || Ember.keys;
24 |
25 | keys(component).forEach((key) => {
26 | if (key.substr(0, 5) === 'data-' && !key.match(/Binding$/)) {
27 | component.get('attributeBindings').pushObject(key);
28 | }
29 | });
30 | }
31 | });
32 |
--------------------------------------------------------------------------------
/addon/components/lazy-image.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import ImageLoadMixin from '../mixins/image-load';
3 | import LazyImageMixin from '../mixins/lazy-image';
4 | import InViewportMixin from 'ember-in-viewport';
5 |
6 | const { on, get, set, computed, Component } = Ember;
7 |
8 | export default Component.extend(InViewportMixin, ImageLoadMixin, LazyImageMixin, {
9 | classNames: ['lazy-image-container'],
10 |
11 | concatenatedProperties: ['class'],
12 |
13 | class: ['lazy-image'],
14 |
15 | _classJoin: on('init', function() {
16 | const classArray = get(this, 'class');
17 | set(this, 'class', classArray.join(' '));
18 | }),
19 |
20 | _setupAttributes() {
21 | const img = this.$('img');
22 | const component = this;
23 | const keys = Object.keys || Ember.keys;
24 |
25 | keys(component).forEach((key) => {
26 | if (key.substr(0, 5) === 'data-' && !key.match(/Binding$/)) {
27 | img.attr(key, component.get(key));
28 | }
29 | });
30 | },
31 |
32 | useDimensionsAttrs: computed('width', 'height', function() {
33 | return !this.get('width') || !this.get('height') ? false : true;
34 | })
35 | });
36 |
--------------------------------------------------------------------------------
/addon/lib/cache.js:
--------------------------------------------------------------------------------
1 | import StorageObject from 'ember-local-storage/session/object';
2 |
3 | export default StorageObject.extend({
4 | storageKey: 'ember-lazy-images'
5 | });
--------------------------------------------------------------------------------
/addon/mixins/image-load.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | const { on, set, run, Mixin, computed, getWithDefault } = Ember;
4 |
5 | export default Mixin.create({
6 | loaded: false,
7 | errorThrown: false,
8 |
9 | classNameBindings: ['loaded', 'errorThrown'],
10 |
11 | defaultErrorText: computed('errorText', function() {
12 | return getWithDefault(this, 'errorText', 'Image failed to load');
13 | }),
14 |
15 | _resolveImage: on('didRender', function() {
16 | const component = this;
17 | const image = component.$('img');
18 | const isCached = image[0].complete;
19 |
20 | if (!isCached) {
21 | image.one('load', () => {
22 | image.off('error');
23 | run.schedule('afterRender', component, () => set(component, 'loaded', true));
24 | });
25 |
26 | image.one('error', () => {
27 | image.off('load');
28 | run.schedule('afterRender', component, () => set(component, 'errorThrown', true));
29 | });
30 | } else {
31 | run.schedule('afterRender', component, () => set(component, 'loaded', true));
32 | }
33 | })
34 | });
35 |
--------------------------------------------------------------------------------
/addon/mixins/lazy-image.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Cache from '../lib/cache';
3 |
4 | const { on, get, set, Mixin, computed, setProperties } = Ember;
5 | const dasherize = Ember.String.dasherize;
6 |
7 | export default Mixin.create({
8 | didInsertElement() {
9 | setProperties(this, {
10 | viewportScrollSensitivity: 20,
11 | viewportListeners: [
12 | { context: window, event: 'scroll.scrollable' },
13 | { context: window, event: 'resize.resizable' },
14 | { context: document, event: 'touchmove.scrollable' }
15 | ]
16 | });
17 |
18 | this._super(...arguments);
19 | },
20 |
21 | _cache: Cache.create(),
22 |
23 | lazyUrl: null,
24 |
25 | handleDidRender: on('didRender', function() {
26 | this._setupAttributes();
27 | }),
28 |
29 | handleImageUrl: on('didInitAttrs', function() {
30 | this._setImageUrl();
31 | }),
32 |
33 | _setImageUrl: on('didEnterViewport', function() {
34 | const url = get(this, 'url');
35 | const cache = get(this, '_cache');
36 | const lazyUrl = get(this, 'lazyUrl');
37 | const cacheKey = get(this, '_cacheKey');
38 | const viewportEntered = get(this, 'viewportEntered');
39 |
40 | if (cacheKey && get(cache, cacheKey)) {
41 | set(this, 'lazyUrl', url);
42 | }
43 |
44 | if (viewportEntered && lazyUrl === null) {
45 | set(this, 'lazyUrl', url);
46 |
47 | if (cacheKey) {
48 | set(cache, cacheKey, true);
49 | }
50 | }
51 | }),
52 |
53 | _cacheKey: computed('url', function() {
54 | var url = this.get('url');
55 | var key;
56 |
57 | if (url) {
58 | key = dasherize(url.replace(/^http[s]?\:\/\/|\.|\//g, ''));
59 | }
60 |
61 | if (key) {
62 | return key;
63 | }
64 | })
65 | });
66 |
--------------------------------------------------------------------------------
/app/components/lazy-background-image.js:
--------------------------------------------------------------------------------
1 | import LazyBackgroundImage from 'ember-lazy-image/components/lazy-background-image';
2 | export default LazyBackgroundImage;
3 |
--------------------------------------------------------------------------------
/app/components/lazy-image.js:
--------------------------------------------------------------------------------
1 | import LazyImage from 'ember-lazy-image/components/lazy-image';
2 | export default LazyImage;
3 |
--------------------------------------------------------------------------------
/app/templates/components/lazy-background-image.hbs:
--------------------------------------------------------------------------------
1 | {{#if hasBlock}}
2 | {{#if errorThrown}}
3 |
{{defaultErrorText}}
4 | {{else}}
5 | {{yield}}
6 | {{/if}}
7 | {{else}}
8 | {{#if errorThrown}}
9 | {{defaultErrorText}}
10 | {{else}}
11 |
12 |
28 |
29 | {{/if}}
30 | {{/if}}
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/templates/components/lazy-image.hbs:
--------------------------------------------------------------------------------
1 | {{#if hasBlock}}
2 | {{#if errorThrown}}
3 | {{defaultErrorText}}
4 | {{else}}
5 | {{yield}}
6 | {{/if}}
7 | {{else}}
8 | {{#if errorThrown}}
9 | {{defaultErrorText}}
10 | {{else}}
11 |
12 |
28 |
29 | {{/if}}
30 | {{/if}}
31 |
32 | {{#if useDimensionsAttrs}}
33 |
34 | {{else}}
35 |
36 | {{/if}}
37 |
38 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-lazy-image",
3 | "dependencies": {
4 | "jquery": "^2.1.4",
5 | "ember": "1.13.10",
6 | "ember-resolver": "~0.1.21",
7 | "loader.js": "ember-cli/loader.js#3.2.1",
8 | "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.5",
9 | "ember-cli-test-loader": "ember-cli/ember-cli-test-loader#0.1.3",
10 | "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5",
11 | "ember-qunit": "0.4.15",
12 | "ember-qunit-notifications": "0.1.0",
13 | "qunit": "~1.20.0"
14 | },
15 | "resolutions": {
16 | "qunit-notifications": "~0.1.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 |
3 | module.exports = {
4 | scenarios: [{
5 | name: 'ember-release',
6 | dependencies: {
7 | 'ember': 'components/ember#release'
8 | },
9 | resolutions: {
10 | 'ember': 'release'
11 | }
12 | }, {
13 | name: 'ember-beta',
14 | dependencies: {
15 | 'ember': 'components/ember#beta'
16 | },
17 | resolutions: {
18 | 'ember': 'beta'
19 | }
20 | }, {
21 | name: 'ember-canary',
22 | dependencies: {
23 | 'ember': 'components/ember#canary'
24 | },
25 | resolutions: {
26 | 'ember': 'canary'
27 | }
28 | }]
29 | };
30 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(/* environment, appConfig */) {
4 | return { };
5 | };
6 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | /* global require, module */
2 | var EmberApp = require('ember-cli/lib/broccoli/ember-addon');
3 |
4 | module.exports = function(defaults) {
5 | var app = new EmberApp(defaults, {
6 | // Add options here
7 | });
8 |
9 | /*
10 | This build file specifes the options for the dummy test app of this
11 | addon, located in `/tests/dummy`
12 | This build file does *not* influence how the addon or the app using it
13 | behave. You most likely want to be modifying `./index.js` or app's build file
14 | */
15 |
16 | return app.toTree();
17 | };
18 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 | 'use strict';
3 |
4 | module.exports = {
5 | name: 'ember-lazy-image',
6 |
7 | included: function emberLazyImageIncluded(app) {
8 | this._super.included(app);
9 |
10 | app.import('vendor/lazy-image/lazy-image.css');
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-lazy-image",
3 | "version": "0.0.14",
4 | "description": "Lazy image loading for Ember apps",
5 | "directories": {
6 | "doc": "doc",
7 | "test": "tests"
8 | },
9 | "scripts": {
10 | "start": "ember server",
11 | "build": "ember build",
12 | "test": "ember try:testall"
13 | },
14 | "repository": "https://github.com/twokul/ember-lazy-image/",
15 | "engines": {
16 | "node": ">= 0.10.0"
17 | },
18 | "author": "Alex Navasardyan",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "broccoli-asset-rev": "^2.3.0",
22 | "ember-cli": "1.13.8",
23 | "ember-cli-app-version": "1.0.0",
24 | "ember-cli-content-security-policy": "0.4.0",
25 | "ember-cli-dependency-checker": "^1.1.0",
26 | "ember-cli-htmlbars": "1.0.1",
27 | "ember-cli-htmlbars-inline-precompile": "^0.3.1",
28 | "ember-cli-ic-ajax": "0.2.1",
29 | "ember-cli-inject-live-reload": "^1.3.1",
30 | "ember-cli-qunit": "1.0.3",
31 | "ember-cli-uglify": "^1.2.0",
32 | "ember-disable-prototype-extensions": "^1.0.0",
33 | "ember-export-application-global": "^1.0.5",
34 | "ember-try": "0.0.8",
35 | "phantomjs": "^1.9.18"
36 | },
37 | "dependencies": {
38 | "ember-cli-babel": "^5.0.0",
39 | "ember-in-viewport": "^2.0.4",
40 | "ember-local-storage": "0.0.5"
41 | },
42 | "keywords": [
43 | "ember-addon",
44 | "image",
45 | "lazy load"
46 | ],
47 | "ember-addon": {
48 | "configPath": "tests/dummy/config"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/testem.json:
--------------------------------------------------------------------------------
1 | {
2 | "framework": "qunit",
3 | "test_page": "tests/index.html?hidepassed",
4 | "disable_watching": true,
5 | "launch_in_ci": [
6 | "PhantomJS"
7 | ],
8 | "launch_in_dev": [
9 | "PhantomJS",
10 | "Chrome"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/tests/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "location",
6 | "setTimeout",
7 | "$",
8 | "-Promise",
9 | "define",
10 | "console",
11 | "visit",
12 | "exists",
13 | "fillIn",
14 | "click",
15 | "keyEvent",
16 | "triggerEvent",
17 | "find",
18 | "findWithAssert",
19 | "wait",
20 | "DS",
21 | "andThen",
22 | "currentURL",
23 | "currentPath",
24 | "currentRouteName"
25 | ],
26 | "node": false,
27 | "browser": false,
28 | "boss": true,
29 | "curly": false,
30 | "debug": false,
31 | "devel": false,
32 | "eqeqeq": true,
33 | "evil": true,
34 | "forin": false,
35 | "immed": false,
36 | "laxbreak": false,
37 | "newcap": true,
38 | "noarg": true,
39 | "noempty": false,
40 | "nonew": false,
41 | "nomen": false,
42 | "onevar": false,
43 | "plusplus": false,
44 | "regexp": false,
45 | "undef": true,
46 | "sub": true,
47 | "strict": false,
48 | "white": false,
49 | "eqnull": true,
50 | "esnext": true
51 | }
52 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Resolver from 'ember/resolver';
3 | import loadInitializers from 'ember/load-initializers';
4 | import config from './config/environment';
5 |
6 | var App;
7 |
8 | Ember.MODEL_FACTORY_INJECTIONS = true;
9 |
10 | App = Ember.Application.extend({
11 | modulePrefix: config.modulePrefix,
12 | podModulePrefix: config.podModulePrefix,
13 | Resolver: Resolver
14 | });
15 |
16 | loadInitializers(App, config.modulePrefix);
17 |
18 | export default App;
19 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/components/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/controllers/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/helpers/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy
7 |
8 |
9 |
10 | {{content-for 'head'}}
11 |
12 |
13 |
14 |
15 | {{content-for 'head-footer'}}
16 |
17 |
18 | {{content-for 'body'}}
19 |
20 |
21 |
22 |
23 | {{content-for 'body-footer'}}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/models/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import config from './config/environment';
3 |
4 | var Router = Ember.Router.extend({
5 | location: config.locationType
6 | });
7 |
8 | export default Router.map(function() {
9 | });
10 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/routes/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/routes/index.js:
--------------------------------------------------------------------------------
1 | import Ember from "ember";
2 |
3 | export default Ember.Route.extend({
4 | model: function() {
5 | return Ember.A([{
6 | text: 'Yehuda Katz',
7 | url: 'http://emberjs.com/images/team/ykatz.jpg'
8 | }, {
9 | text: 'Tom Dale',
10 | url: 'http://emberjs.com/images/team/tdale.jpg'
11 | }, {
12 | text: 'Peter Wagenet',
13 | url: 'http://emberjs.com/images/team/pwagenet.jpg'
14 | }, {
15 | text: 'Trek Glowacki',
16 | url: 'http://emberjs.com/images/team/tglowaki.jpg'
17 | }, {
18 | text: 'Erik Bryn',
19 | url: 'http://emberjs.com/images/team/ebryn.jpg'
20 | }, {
21 | text: 'Kris Selden',
22 | url: 'http://emberjs.com/images/team/kselden.jpg'
23 | }, {
24 | text: 'Stefan Penner',
25 | url: 'http://emberjs.com/images/team/spenner.jpg'
26 | }, {
27 | text: 'Leah Silber',
28 | url: 'http://emberjs.com/images/team/lsilber.jpg'
29 | }, {
30 | text: 'Alex Matchneer',
31 | url: 'http://emberjs.com/images/team/amatchneer.jpg'
32 | }, {
33 | text: 'Robert Jackson',
34 | url: 'http://emberjs.com/images/team/rjackson.jpg'
35 | }, {
36 | text: 'Igor Terzic',
37 | url: 'http://emberjs.com/images/team/iterzic.jpeg'
38 | }]);
39 | }
40 | });
41 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/styles/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/styles/app.css
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 | Welcome to Ember.js
2 |
3 | {{outlet}}
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/templates/components/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/templates/index.hbs:
--------------------------------------------------------------------------------
1 | {{#each model as |person|}}
2 | {{lazy-image url=person.url alt=person.text}}
3 | {{/each}}
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/views/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/views/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 |
3 | module.exports = function(environment) {
4 | var ENV = {
5 | modulePrefix: 'dummy',
6 | environment: environment,
7 | baseURL: '/',
8 | locationType: 'auto',
9 | EmberENV: {
10 | FEATURES: {
11 | // Here you can enable experimental features on an ember canary build
12 | // e.g. 'with-controller': true
13 | }
14 | },
15 | contentSecurityPolicy: {
16 | 'img-src': "'self' emberjs.com",
17 | 'style-src': "'self' 'unsafe-inline'"
18 | },
19 |
20 | APP: {
21 | // Here you can pass flags/options to your application instance
22 | // when it is created
23 | },
24 |
25 | viewportConfig: {
26 | viewportUseRAF: false
27 | }
28 | };
29 |
30 | if (environment === 'development') {
31 | // ENV.APP.LOG_RESOLVER = true;
32 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
33 | // ENV.APP.LOG_TRANSITIONS = true;
34 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
35 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
36 | }
37 |
38 | if (environment === 'test') {
39 | // Testem prefers this...
40 | ENV.baseURL = '/';
41 | ENV.locationType = 'none';
42 |
43 | ENV.EmberENV.RAISE_ON_DEPRECATION = true;
44 |
45 | // keep test console output quieter
46 | ENV.APP.LOG_ACTIVE_GENERATION = false;
47 | ENV.APP.LOG_VIEW_LOOKUPS = false;
48 |
49 | ENV.APP.rootElement = '#ember-testing';
50 | }
51 |
52 | if (environment === 'production') {
53 |
54 | }
55 |
56 | return ENV;
57 | };
58 |
--------------------------------------------------------------------------------
/tests/dummy/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/tests/helpers/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember/resolver';
2 | import config from '../../config/environment';
3 |
4 | var resolver = Resolver.create();
5 |
6 | resolver.namespace = {
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix
9 | };
10 |
11 | export default resolver;
12 |
--------------------------------------------------------------------------------
/tests/helpers/start-app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Application from '../../app';
3 | import Router from '../../router';
4 | import config from '../../config/environment';
5 |
6 | export default function startApp(attrs) {
7 | var application;
8 |
9 | var attributes = Ember.merge({}, config.APP);
10 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override;
11 |
12 | Ember.run(function() {
13 | application = Application.create(attributes);
14 | application.setupForTesting();
15 | application.injectTestHelpers();
16 | });
17 |
18 | return application;
19 | }
20 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy Tests
7 |
8 |
9 |
10 | {{content-for 'head'}}
11 | {{content-for 'test-head'}}
12 |
13 |
14 |
15 |
16 |
17 | {{content-for 'head-footer'}}
18 | {{content-for 'test-head-footer'}}
19 |
20 |
21 |
22 | {{content-for 'body'}}
23 | {{content-for 'test-body'}}
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{content-for 'body-footer'}}
31 | {{content-for 'test-body-footer'}}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import resolver from './helpers/resolver';
2 | import {
3 | setResolver
4 | } from 'ember-qunit';
5 |
6 | setResolver(resolver);
7 |
--------------------------------------------------------------------------------
/tests/unit/components/lazy-image-test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import {
3 | moduleForComponent,
4 | test
5 | } from 'ember-qunit';
6 |
7 | import Cache from 'ember-lazy-image/lib/cache';
8 | import hbs from 'htmlbars-inline-precompile';
9 |
10 | moduleForComponent('lazy-image', 'LazyImageComponent', {
11 | unit: true,
12 |
13 | beforeEach() {
14 | window.sessionStorage.clear();
15 | }
16 | });
17 |
18 | const { run } = Ember;
19 | const get = Ember.get;
20 |
21 | const imageSelector = '.lazy-image';
22 | const placeholderSelector = '.lazy-image-placeholder';
23 | const errorMessageSelector = '.lazy-image-error-message';
24 | const imageContainerSelector = '.lazy-image-container';
25 |
26 | test('it has correct defaults', function(assert) {
27 | assert.expect(5);
28 |
29 | const component = this.subject();
30 |
31 | assert.equal(get(component, 'loaded'), false);
32 | assert.equal(get(component, 'errorThrown'), false);
33 | assert.equal(get(component, 'lazyUrl'), null);
34 | assert.equal(get(component, 'defaultErrorText'), 'Image failed to load');
35 | assert.equal(get(component, 'class'), 'lazy-image');
36 | });
37 |
38 | test('it renders default placeholder', function(assert) {
39 | assert.expect(1);
40 |
41 | this.render(hbs`{{lazy-image}}`);
42 |
43 | assert.ok(this.$(placeholderSelector).length > 0, 'placeholder is correctly rendered');
44 | });
45 |
46 | test('it renders default error message if image fails to load', function(assert) {
47 | assert.expect(2);
48 |
49 | const component = this.subject({
50 | errorThrown: true
51 | });
52 |
53 | this.render();
54 |
55 | assert.ok(component.$(errorMessageSelector).length > 0, 'error message is correctly rendered');
56 | assert.ok(component.$(errorMessageSelector + ':contains("' + 'Image failed to load' + '")'), 'default error message is rendered correctly');
57 | });
58 |
59 | test('it leverages cache', function(assert) {
60 | run(() => {
61 | Cache.create();
62 | });
63 |
64 | assert.expect(1);
65 |
66 | const component = this.subject({
67 | url: 'http://emberjs.com/images/team/tdale.jpg'
68 | });
69 |
70 | this.render();
71 |
72 | run(() => {
73 | component.set('viewportEntered', true);
74 | component.trigger('didEnterViewport');
75 | });
76 |
77 | let lazyImages = window.sessionStorage['ember-lazy-images'];
78 | let cache = lazyImages ? JSON.parse(lazyImages) : lazyImages;
79 |
80 | assert.deepEqual(cache, {
81 | emberjscomimagesteamtdalejpg: true
82 | });
83 | });
84 |
85 | test('`width` and `height` bindings work correctly', function(assert) {
86 | assert.expect(2);
87 |
88 | const component = this.subject({
89 | width: 400,
90 | height: 400
91 | });
92 |
93 | this.render();
94 |
95 | assert.equal(component.$('img').attr('width'), 400, 'width is correct');
96 | assert.equal(component.$('img').attr('height'), 400, 'height is correct');
97 | });
98 |
99 | test('`width` and `height` are not used if set to 0 or unset', function(assert) {
100 | assert.expect(2);
101 |
102 | const component = this.subject({
103 | width: 400
104 | });
105 |
106 | this.render();
107 |
108 | assert.equal(component.$('img').attr('width'), undefined, 'width is not used');
109 | assert.equal(component.$('img').attr('height'), undefined, 'height is not used');
110 | });
111 |
112 | test('`data-*` attribute bindings work correctly', function(assert) {
113 | assert.expect(1);
114 |
115 | const component = this.subject({
116 | 'data-person-id': 1234
117 | });
118 |
119 | this.render();
120 |
121 | assert.equal(component.$('img').attr('data-person-id'), 1234, 'data attribute is correct');
122 | });
123 |
124 | test('passing class names for the
element', function(assert) {
125 | assert.expect(1);
126 |
127 | const component = this.subject({
128 | class: 'img-responsive image-thumbnail'
129 | });
130 |
131 | this.render();
132 |
133 | const expected = 'lazy-image img-responsive image-thumbnail';
134 | assert.equal(component.$('img').attr('class'), expected);
135 | });
136 |
137 | test('passing alt attribute for the
element', function(assert) {
138 | assert.expect(1);
139 |
140 | const component = this.subject({
141 | alt: 'alternate text'
142 | });
143 |
144 | this.render();
145 |
146 | const expected = 'alternate text';
147 | assert.equal(component.$('img').attr('alt'), expected);
148 | });
149 |
--------------------------------------------------------------------------------
/vendor/lazy-image/lazy-image.css:
--------------------------------------------------------------------------------
1 | .lazy-image-container {
2 | position: relative;
3 | min-height: 200px;
4 | }
5 |
6 | .lazy-image-container img {
7 | z-index: 0;
8 | opacity: 0;
9 | }
10 |
11 | .lazy-image-container.loaded img {
12 | opacity: 1;
13 | }
14 |
15 | .lazy-image-error-message {
16 | position: absolute;
17 | top: 50%;
18 | right: 50%;
19 | }
20 |
21 | .lazy-image-container.loaded .lazy-image-error-message {
22 | display: none;
23 | }
24 |
25 | .lazy-image-container.loaded .lazy-image-placeholder {
26 | display: none;
27 | }
28 |
29 | .lazy-background-image.loaded .lazy-image-placeholder {
30 | display: none;
31 | }
32 |
33 | .lazy-image-placeholder {
34 | position: absolute;
35 | top: 50%;
36 | right: 50%;
37 | }
38 |
39 | .lazy-background-image {
40 | background-size: cover;
41 | background-repeat: no-repeat;
42 | min-height: 270px;
43 | }
44 |
--------------------------------------------------------------------------------