devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
335 lines (334 loc) • 13.7 kB
JavaScript
/**
* DevExtreme (framework/html/view_engine.js)
* Version: 18.1.3
* Build date: Tue May 15 2018
*
* Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
require("../../integration/jquery");
var $ = require("jquery"),
version = require("../../core/version"),
window = require("../../core/utils/window").getWindow(),
Class = require("../../core/class"),
Callbacks = require("../../core/utils/callbacks"),
commonUtils = require("../../core/utils/common"),
each = require("../../core/utils/iterator").each,
inArray = require("../../core/utils/array").inArray,
errors = require("../errors"),
domUtils = require("../../core/utils/dom"),
when = require("../../core/utils/deferred").when,
ajax = require("../../core/utils/ajax"),
_VIEW_ROLE = "dxView",
_LAYOUT_ROLE = "dxLayout",
MARKUP_TEMPLATE_MARKER = "MarkupTemplate:";
require("./view_engine_components");
var ViewEngine = Class.inherit({
ctor: function(options) {
options = options || {};
this.$root = options.$root;
this.device = options.device || {};
this.dataOptionsAttributeName = options.dataOptionsAttributeName || "data-options";
this._templateMap = {};
this._pendingViewContainer = null;
this.markupLoaded = Callbacks();
this._templateContext = options.templateContext;
this._$skippedMarkup = $();
if (void 0 !== options.templatesVersion && options.templateCacheStorage && this._isReleaseVersion()) {
this._templateCacheEnabled = true;
this._templatesVersion = "v_" + options.templatesVersion;
this._templateCacheStorage = options.templateCacheStorage;
this._templateCacheKey = "dxTemplateCache_" + version + "_" + JSON.stringify(this.device)
}
},
_isReleaseVersion: function() {
return !/http:\/\/localhost/.test(window.location.href)
},
_enumerateTemplates: function(processFn) {
var that = this;
each(that._templateMap, function(name, templatesByRoleMap) {
each(templatesByRoleMap, function(role, templates) {
each(templates, function(index, template) {
processFn(template)
})
})
})
},
_findComponent: function(name, role) {
var components = (this._templateMap[name] || {})[role] || [],
filter = this._templateContext && this._templateContext.option() || {};
components = this._filterTemplates(filter, components);
this._checkMatchedTemplates(components);
return components[0]
},
_findTemplate: function(name, role) {
var component = this._findComponent(name, role);
if (!component) {
this._clearCache();
throw errors.Error("E3013", role, name)
}
var $result, $template = component.element();
if (!component._isStaticComponentsCreated) {
domUtils.createComponents($template, ["dxContent", "dxContentPlaceholder", "dxTransition"]);
component._isStaticComponentsCreated = true
}
$result = $template.clone().removeClass("dx-hidden");
return $result
},
_clearCache: function() {
if (this._templateCacheEnabled) {
this._templateCacheStorage.removeItem(this._templateCacheKey)
}
},
_loadTemplatesFromMarkupCore: function($markup) {
var that = this;
if ($markup.find("[data-dx-role]").length) {
throw errors.Error("E3019")
}
that.markupLoaded.fire({
markup: $markup
});
var components = domUtils.createComponents($markup, [_VIEW_ROLE, _LAYOUT_ROLE]);
each(components, function(index, component) {
var $element = component.element();
$element.addClass("dx-hidden");
that._registerTemplateComponent(component);
component.element().detach()
});
var $skipped = $markup.filter("script");
$skipped.appendTo(that.$root);
that._$skippedMarkup = that._$skippedMarkup.add($skipped)
},
_registerTemplateComponent: function(component) {
var role = component.NAME,
options = component.option(),
templateName = options.name,
componentsByRoleMap = this._templateMap[templateName] || {};
componentsByRoleMap[role] = componentsByRoleMap[role] || [];
componentsByRoleMap[role].push(component);
this._templateMap[templateName] = componentsByRoleMap
},
_applyPartialViews: function($render) {
var that = this;
domUtils.createComponents($render, ["dxViewPlaceholder"]);
each($render.find(".dx-view-placeholder"), function() {
var $partialPlaceholder = $(this);
if ($partialPlaceholder.children().length) {
return
}
var viewName = $partialPlaceholder.data("dxViewPlaceholder").option("viewName"),
$view = that._findTemplate(viewName, _VIEW_ROLE);
that._applyPartialViews($view);
$partialPlaceholder.append($view);
$view.removeClass("dx-hidden")
})
},
_ajaxImpl: function() {
return ajax.sendRequest.apply($, arguments)
},
_loadTemplatesFromURL: function(url) {
var that = this,
winPhonePrefix = this._getWinPhonePrefix(),
deferred = $.Deferred();
url = winPhonePrefix + url;
this._ajaxImpl({
url: url,
dataType: "html"
}).done(function(data) {
that._loadTemplatesFromMarkupCore(domUtils.createMarkupFromString(data));
deferred.resolve()
}).fail(function(jqXHR, textStatus, errorThrown) {
var error = errors.Error("E3021", url, errorThrown);
deferred.reject(error)
});
return deferred.promise()
},
_getWinPhonePrefix: function() {
if (window.location.protocol.indexOf("wmapp") >= 0) {
return window.location.protocol + "www/"
}
return ""
},
_loadExternalTemplates: function() {
var tasks = [],
that = this;
$("head").find("link[rel='dx-template']").each(function(index, link) {
var task = that._loadTemplatesFromURL($(link).attr("href"));
tasks.push(task)
});
return when.apply($, tasks)
},
_processTemplates: function() {
var that = this;
each(that._templateMap, function(name, templatesByRoleMap) {
each(templatesByRoleMap, function(role, templates) {
that._filterTemplatesByDevice(templates)
})
});
that._enumerateTemplates(function(template) {
that._applyPartialViews(template.element())
})
},
_filterTemplatesByDevice: function(components) {
var filteredComponents = this._filterTemplates(this.device, components);
each(components, function(index, component) {
if (inArray(component, filteredComponents) < 0) {
component.element().remove()
}
});
components.length = 0;
components.push.apply(components, filteredComponents)
},
_filterTemplates: function(filter, components) {
return commonUtils.findBestMatches(filter, components, function(component) {
return component.option()
})
},
_checkMatchedTemplates: function(bestMatches) {
if (bestMatches.length > 1) {
var message = "";
each(bestMatches, function(index, match) {
message += match.element().attr("data-options") + "\r\n"
});
throw errors.Error("E3020", message, JSON.stringify(this.device))
}
},
_wrapViewDefaultContent: function($viewTemplate) {
$viewTemplate.wrapInner('<div class="dx-full-height"></div>');
$viewTemplate.children().eq(0).dxContent({
targetPlaceholder: "content"
})
},
_initDefaultLayout: function() {
this._$defaultLayoutTemplate = $('<div class="dx-full-height" data-options="dxLayout : { name: \'default\' } "> \n <div class="dx-full-height" data-options="dxContentPlaceholder : { name: \'content\' } " ></div> \n</div>');
domUtils.createComponents(this._$defaultLayoutTemplate)
},
_getDefaultLayoutTemplate: function() {
return this._$defaultLayoutTemplate.clone()
},
applyLayout: function($view, $layout) {
if (void 0 === $layout || 0 === $layout.length) {
$layout = this._getDefaultLayoutTemplate()
}
if (0 === $view.children(".dx-content").length) {
this._wrapViewDefaultContent($view)
}
var $toMerge = $().add($layout).add($view);
var $placeholderContents = $toMerge.find(".dx-content");
each($placeholderContents, function() {
var $placeholderContent = $(this);
var placeholderId = $placeholderContent.attr("data-dx-target-placeholder-id");
var $placeholder = $toMerge.find(".dx-content-placeholder-" + placeholderId);
$placeholder.empty();
$placeholder.append($placeholderContent)
});
for (var i = $placeholderContents.length; i >= 0; i--) {
var $item = $placeholderContents.eq(i);
if (!$item.is(".dx-content-placeholder .dx-content")) {
$item.remove()
}
}
return $layout
},
_loadTemplatesFromCache: function() {
if (!this._templateCacheEnabled) {
return
}
var cache;
var fromJSONInterceptor = function(key, value) {
if ("string" === typeof value && 0 === value.indexOf(MARKUP_TEMPLATE_MARKER)) {
var data = JSON.parse(value.substr(MARKUP_TEMPLATE_MARKER.length)),
type = data.type,
options = data.options,
$markup = domUtils.createMarkupFromString(data.markup);
options.fromCache = true;
return $markup[type](options)[type]("instance")
} else {
if ("skippedMarkup" === key) {
return $("<div>").append(domUtils.createMarkupFromString(value)).contents()
}
}
return value
};
var toParse = this._templateCacheStorage.getItem(this._templateCacheKey);
if (toParse) {
try {
var cacheContainer = JSON.parse(toParse, fromJSONInterceptor);
cache = cacheContainer[this._templatesVersion]
} catch (e) {
this._clearCache()
}
}
if (!cache) {
return
}
this._templateMap = cache.templates;
this.$root.append(cache.skippedMarkup);
return true
},
_putTemplatesToCache: function() {
if (!this._templateCacheEnabled) {
return
}
var toJSONInterceptor = function(key, value) {
if (value && value.element) {
return MARKUP_TEMPLATE_MARKER + JSON.stringify({
markup: value.element().prop("outerHTML"),
options: value.option(),
type: value.NAME
})
} else {
if ("skippedMarkup" === key) {
return $("<div>").append(value.clone()).html()
}
}
return value
};
var cacheContainer = {};
cacheContainer[this._templatesVersion] = {
templates: this._templateMap,
skippedMarkup: this._$skippedMarkup
};
this._templateCacheStorage.setItem(this._templateCacheKey, JSON.stringify(cacheContainer, toJSONInterceptor, 4))
},
init: function() {
var that = this;
this._initDefaultLayout();
if (!this._loadTemplatesFromCache()) {
that._loadTemplatesFromMarkupCore(that.$root.children());
return this._loadExternalTemplates().done(function() {
that._processTemplates();
that._putTemplatesToCache()
})
} else {
return $.Deferred().resolve().promise()
}
},
getViewTemplate: function(viewName) {
return this._findTemplate(viewName, _VIEW_ROLE)
},
getViewTemplateInfo: function(name) {
return this._findComponent(name, _VIEW_ROLE)
},
getLayoutTemplate: function(layoutName) {
if (!layoutName) {
return this._getDefaultLayoutTemplate()
}
return this._findTemplate(layoutName, _LAYOUT_ROLE)
},
getLayoutTemplateInfo: function(name) {
return this._findComponent(name, _LAYOUT_ROLE)
},
loadTemplates: function(source) {
var result;
if ("string" === typeof source) {
result = this._loadTemplatesFromURL(source)
} else {
this._loadTemplatesFromMarkupCore(source);
result = $.Deferred().resolve().promise()
}
return result.done(this._processTemplates.bind(this))
}
});
exports.ViewEngine = ViewEngine;